后端迁移任务仓储模块
This commit is contained in:
+58
-43
@@ -1,13 +1,31 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
import { query, withTransaction } from '../db/client.js'
|
import { query, withTransaction } from '../db/client.js'
|
||||||
|
import type { PoolClient } from 'pg'
|
||||||
|
|
||||||
/** @typedef {import('../types/repository-inputs.js').TaskCreateInput} TaskCreateInput */
|
import type {
|
||||||
/** @typedef {import('../types/repository-inputs.js').TaskListQueryInput} TaskListQueryInput */
|
TaskCreateInput,
|
||||||
/** @typedef {import('../types/repository-inputs.js').TaskTencentContextPatch} TaskTencentContextPatch */
|
TaskListQueryInput,
|
||||||
/** @typedef {import('../types/repository-inputs.js').TaskUpdatePatch} TaskUpdatePatch */
|
TaskTencentContextPatch,
|
||||||
/** @typedef {import('../types/repository-rows.js').TaskListQueryResult} TaskListQueryResult */
|
TaskUpdatePatch,
|
||||||
/** @typedef {import('../types/repository-rows.js').TaskRow} TaskRow */
|
} from '../types/repository-inputs.js'
|
||||||
|
import type {
|
||||||
|
TaskListQueryResult,
|
||||||
|
TaskRow,
|
||||||
|
} from '../types/repository-rows.js'
|
||||||
|
|
||||||
|
type TaskQueryExecutor = (text: string, params?: unknown[]) => Promise<{ rows: unknown[] }>
|
||||||
|
|
||||||
|
type TencentBrowserContextRow = {
|
||||||
|
browser_session_id: string
|
||||||
|
login_type: string
|
||||||
|
nickname: string
|
||||||
|
role_id: string
|
||||||
|
role_name: string
|
||||||
|
area: string
|
||||||
|
partition_name: string
|
||||||
|
screenshot_path: string
|
||||||
|
artifacts_json: string | Record<string, unknown>
|
||||||
|
state_json: string | Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
const TASK_FIELDS = `
|
const TASK_FIELDS = `
|
||||||
ft.*,
|
ft.*,
|
||||||
@@ -55,28 +73,25 @@ const TASK_JOINS = `
|
|||||||
) inv ON TRUE
|
) inv ON TRUE
|
||||||
`
|
`
|
||||||
|
|
||||||
function buildTaskSelect(extraFields = '') {
|
function buildTaskSelect(extraFields = ''): string {
|
||||||
const normalizedExtra = String(extraFields || '').trim()
|
const normalizedExtra = String(extraFields || '').trim()
|
||||||
const fieldSql = normalizedExtra ? `${TASK_FIELDS}, ${normalizedExtra}` : TASK_FIELDS
|
const fieldSql = normalizedExtra ? `${TASK_FIELDS}, ${normalizedExtra}` : TASK_FIELDS
|
||||||
return `SELECT ${fieldSql} ${TASK_JOINS}`
|
return `SELECT ${fieldSql} ${TASK_JOINS}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<TaskRow[]>} */
|
export async function listTasksByOrderId(orderId: number | string): Promise<TaskRow[]> {
|
||||||
export async function listTasksByOrderId(orderId) {
|
const result = await query<TaskRow>(
|
||||||
const result = await query(
|
|
||||||
`${buildTaskSelect()}
|
`${buildTaskSelect()}
|
||||||
WHERE ft.order_id = $1
|
WHERE ft.order_id = $1
|
||||||
ORDER BY ft.id ASC`,
|
ORDER BY ft.id ASC`,
|
||||||
[Number(orderId)],
|
[Number(orderId)],
|
||||||
)
|
)
|
||||||
return /** @type {TaskRow[]} */ (result.rows)
|
return result.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<TaskRow | null>} */
|
export async function createTask(input: TaskCreateInput): Promise<TaskRow | null> {
|
||||||
/** @param {TaskCreateInput} input */
|
|
||||||
export async function createTask(input) {
|
|
||||||
return withTransaction(async (client) => {
|
return withTransaction(async (client) => {
|
||||||
const taskResult = await client.query(
|
const taskResult = await client.query<{ id: number }>(
|
||||||
`
|
`
|
||||||
INSERT INTO fulfillment_tasks (
|
INSERT INTO fulfillment_tasks (
|
||||||
order_id,
|
order_id,
|
||||||
@@ -147,13 +162,11 @@ export async function createTask(input) {
|
|||||||
await upsertTencentBrowserContextWithClient(client, taskId, input.tencentContext, input.createdAt)
|
await upsertTencentBrowserContextWithClient(client, taskId, input.tencentContext, input.createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getTaskByIdWithExecutor(client.query.bind(client), taskId)
|
return getTaskByIdWithExecutor(client.query.bind(client) as TaskQueryExecutor, taskId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<TaskRow | null>} */
|
export async function updateTask(taskId: number | string, patch: TaskUpdatePatch): Promise<TaskRow | null> {
|
||||||
/** @param {TaskUpdatePatch} patch */
|
|
||||||
export async function updateTask(taskId, patch) {
|
|
||||||
const current = await getTaskById(taskId)
|
const current = await getTaskById(taskId)
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return null
|
return null
|
||||||
@@ -222,29 +235,25 @@ export async function updateTask(taskId, patch) {
|
|||||||
}, patch.updated_at || current.updated_at)
|
}, patch.updated_at || current.updated_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getTaskByIdWithExecutor(client.query.bind(client), taskId)
|
return getTaskByIdWithExecutor(client.query.bind(client) as TaskQueryExecutor, taskId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<TaskRow | null>} */
|
export async function getTaskById(taskId: number | string): Promise<TaskRow | null> {
|
||||||
export async function getTaskById(taskId) {
|
|
||||||
return getTaskByIdWithExecutor(query, taskId)
|
return getTaskByIdWithExecutor(query, taskId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<TaskRow | null>} */
|
export async function findTaskByClaimTokenId(claimTokenId: number | string): Promise<TaskRow | null> {
|
||||||
export async function findTaskByClaimTokenId(claimTokenId) {
|
const result = await query<TaskRow>(
|
||||||
const result = await query(
|
|
||||||
`${buildTaskSelect()}
|
`${buildTaskSelect()}
|
||||||
JOIN claim_tokens ctf ON ctf.task_id = ft.id
|
JOIN claim_tokens ctf ON ctf.task_id = ft.id
|
||||||
WHERE ctf.id = $1
|
WHERE ctf.id = $1
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
[Number(claimTokenId)],
|
[Number(claimTokenId)],
|
||||||
)
|
)
|
||||||
return /** @type {TaskRow | null} */ (result.rows[0] || null)
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<TaskListQueryResult>} */
|
|
||||||
/** @param {TaskListQueryInput} [queryInput] */
|
|
||||||
export async function listTasks({
|
export async function listTasks({
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
@@ -255,10 +264,10 @@ export async function listTasks({
|
|||||||
roleId = '',
|
roleId = '',
|
||||||
dateFrom = '',
|
dateFrom = '',
|
||||||
dateTo = '',
|
dateTo = '',
|
||||||
} = /** @type {TaskListQueryInput} */ ({})) {
|
}: TaskListQueryInput = {}): Promise<TaskListQueryResult> {
|
||||||
const offset = (page - 1) * pageSize
|
const offset = (page - 1) * pageSize
|
||||||
const filters = []
|
const filters: string[] = []
|
||||||
const params = []
|
const params: unknown[] = []
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
params.push(status)
|
params.push(status)
|
||||||
@@ -296,7 +305,7 @@ export async function listTasks({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||||
const totalResult = await query(
|
const totalResult = await query<{ [column: string]: unknown, total: number }>(
|
||||||
`
|
`
|
||||||
SELECT COUNT(*)::int AS total
|
SELECT COUNT(*)::int AS total
|
||||||
FROM fulfillment_tasks ft
|
FROM fulfillment_tasks ft
|
||||||
@@ -309,7 +318,7 @@ export async function listTasks({
|
|||||||
|
|
||||||
params.push(pageSize)
|
params.push(pageSize)
|
||||||
params.push(offset)
|
params.push(offset)
|
||||||
const itemsResult = await query(
|
const itemsResult = await query<TaskRow>(
|
||||||
`${buildTaskSelect('oi.sku_code, oi.sku_name, oi.quantity')}
|
`${buildTaskSelect('oi.sku_code, oi.sku_name, oi.quantity')}
|
||||||
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
|
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
@@ -319,14 +328,18 @@ export async function listTasks({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: /** @type {TaskRow[]} */ (itemsResult.rows),
|
items: itemsResult.rows,
|
||||||
total: Number(totalResult.rows[0]?.total || 0),
|
total: Number(totalResult.rows[0]?.total || 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {TaskTencentContextPatch} patch */
|
async function upsertTencentBrowserContextWithClient(
|
||||||
async function upsertTencentBrowserContextWithClient(client, taskId, patch = {}, timestamp) {
|
client: PoolClient,
|
||||||
const currentResult = await client.query(
|
taskId: number | string,
|
||||||
|
patch: TaskTencentContextPatch = {},
|
||||||
|
timestamp: string | undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
const currentResult = await client.query<TencentBrowserContextRow>(
|
||||||
'SELECT * FROM tencent_browser_contexts WHERE task_id = $1 LIMIT 1',
|
'SELECT * FROM tencent_browser_contexts WHERE task_id = $1 LIMIT 1',
|
||||||
[Number(taskId)],
|
[Number(taskId)],
|
||||||
)
|
)
|
||||||
@@ -418,18 +431,20 @@ async function upsertTencentBrowserContextWithClient(client, taskId, patch = {},
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<TaskRow | null>} */
|
async function getTaskByIdWithExecutor(
|
||||||
async function getTaskByIdWithExecutor(executor, taskId) {
|
executor: TaskQueryExecutor,
|
||||||
|
taskId: number | string,
|
||||||
|
): Promise<TaskRow | null> {
|
||||||
const result = await executor(
|
const result = await executor(
|
||||||
`${buildTaskSelect()}
|
`${buildTaskSelect()}
|
||||||
WHERE ft.id = $1
|
WHERE ft.id = $1
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
[Number(taskId)],
|
[Number(taskId)],
|
||||||
)
|
)
|
||||||
return /** @type {TaskRow | null} */ (result.rows[0] || null)
|
return (result.rows[0] as TaskRow | undefined) || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function containsTencentPatch(patch = {}) {
|
function containsTencentPatch(patch: TaskUpdatePatch = {}): boolean {
|
||||||
return [
|
return [
|
||||||
'browser_session_id',
|
'browser_session_id',
|
||||||
'login_type',
|
'login_type',
|
||||||
@@ -39,6 +39,7 @@ export type TaskRow = {
|
|||||||
order_id: number
|
order_id: number
|
||||||
order_item_id: number
|
order_item_id: number
|
||||||
platform_order_id: string
|
platform_order_id: string
|
||||||
|
profile_id: number
|
||||||
task_no: string
|
task_no: string
|
||||||
executor_key: string
|
executor_key: string
|
||||||
task_status: string
|
task_status: string
|
||||||
@@ -46,17 +47,25 @@ export type TaskRow = {
|
|||||||
result_code: string
|
result_code: string
|
||||||
result_message: string
|
result_message: string
|
||||||
inventory_status: string
|
inventory_status: string
|
||||||
|
automation_mode: string
|
||||||
|
requires_claim: boolean
|
||||||
user_action_status: string
|
user_action_status: string
|
||||||
|
attempt_count: number
|
||||||
browser_session_id: string
|
browser_session_id: string
|
||||||
login_type: string
|
login_type: string
|
||||||
|
nickname: string
|
||||||
role_name: string
|
role_name: string
|
||||||
role_id: string
|
role_id: string
|
||||||
|
area: string
|
||||||
|
partition_name: string
|
||||||
claim_token: string
|
claim_token: string
|
||||||
claim_expires_at?: string | null
|
claim_expires_at?: string | null
|
||||||
primary_claim_token: string
|
primary_claim_token: string
|
||||||
primary_claim_token_id: number | null
|
primary_claim_token_id: number | null
|
||||||
|
primary_claim_token_status: string
|
||||||
primary_claim_expires_at?: string | null
|
primary_claim_expires_at?: string | null
|
||||||
primary_inventory_item_id: number | null
|
primary_inventory_item_id: number | null
|
||||||
|
primary_inventory_binding_status: string
|
||||||
inventory_display_value: string
|
inventory_display_value: string
|
||||||
primary_inventory_display_value: string
|
primary_inventory_display_value: string
|
||||||
inventory_credential_type?: string
|
inventory_credential_type?: string
|
||||||
|
|||||||
@@ -233,14 +233,17 @@
|
|||||||
- `src/repositories/task-inventory-binding-repo.ts`
|
- `src/repositories/task-inventory-binding-repo.ts`
|
||||||
16. 核心库存 repository 已迁移到 `.ts`:
|
16. 核心库存 repository 已迁移到 `.ts`:
|
||||||
- `src/repositories/inventory-repo.ts`
|
- `src/repositories/inventory-repo.ts`
|
||||||
|
17. 核心任务 repository 已迁移到 `.ts`:
|
||||||
|
- `src/repositories/task-repo.ts`
|
||||||
|
18. `TaskRow` 已补齐任务读取链路实际使用的主表、领取 token、腾讯上下文、库存绑定字段
|
||||||
|
19. 核心 repository 迁移阶段已收口,Docker 内 `typecheck / build / test` 继续通过
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
第一批继续推进时,建议按这个顺序:
|
第一批继续推进时,建议按这个顺序:
|
||||||
|
|
||||||
1. 继续迁移剩余核心 repository:`task-repo`
|
1. 拆分并迁移 `runtime.js`,把 env 解析、默认配置加载、配置合并分开
|
||||||
2. 拆分并迁移 `runtime.js`,把 env 解析、默认配置加载、配置合并分开
|
2. 为 webhook、库存换码、自动发货补测试
|
||||||
3. 为 webhook、库存换码、自动发货补测试
|
|
||||||
|
|
||||||
## 执行原则
|
## 执行原则
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user