优化订单履约分流与后台筛选

This commit is contained in:
yml2213
2026-09-02 11:59:38 +08:00
parent 50c526a855
commit de63daf1ac
19 changed files with 1269 additions and 162 deletions
@@ -21,6 +21,22 @@ export async function listOrderItemsByOrderId(orderId: number | string): Promise
return listOrderItemsByOrderIdWithExecutor(query, orderId)
}
export async function listOrderItemsByOrderIds(orderIds: number[]): Promise<OrderItemRow[]> {
const normalizedIds = orderIds.map(Number).filter((value) => value > 0)
if (normalizedIds.length === 0) return []
const result = await query<OrderItemRow>(
`
SELECT *
FROM order_items
WHERE order_id = ANY($1::bigint[])
ORDER BY order_id ASC, id ASC
`,
[normalizedIds],
)
return result.rows
}
export async function replaceOrderItems(
orderId: number | string,
items: OrderItemReplaceInput[],
@@ -0,0 +1,73 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { listOrdersWithExecutor, type OrderQueryExecutor } from './order-repo.js'
test('订单列表综合筛选下推到总数和分页查询', async () => {
const calls: Array<{ sql: string; params: unknown[] }> = []
const executor = createExecutor(calls, 3)
const result = await listOrdersWithExecutor(executor, {
page: 2,
pageSize: 10,
keyword: '皮肤店',
provider: '91kaquan',
executorKey: 'kuaishou_ct_assisted',
payStatus: 'paid',
taskStatus: 'manual_review',
dateFrom: '2026-09-01T00:00:00.000Z',
dateTo: '2026-09-02T23:59:59.999Z',
})
assert.equal(result.total, 3)
assert.equal(calls.length, 2)
assert.match(calls[0]?.sql || '', /o\.platform_order_id.*ILIKE \$1/s)
assert.match(calls[0]?.sql || '', /oi\.sku_code.*oi\.sku_name.*ILIKE \$1/s)
assert.match(calls[0]?.sql || '', /kuaishouSendCode'.*itemTitle.*kuaishouSendCode'.*skuNick/s)
assert.match(calls[0]?.sql || '', /o\.provider = \$2/)
assert.match(calls[0]?.sql || '', /o\.pay_status = \$3/)
assert.match(calls[0]?.sql || '', /ft\.task_status = \$4/)
assert.match(calls[0]?.sql || '', /ft\.executor_key = \$5/)
assert.match(calls[0]?.sql || '', /o\.created_at >= \$6/)
assert.match(calls[0]?.sql || '', /o\.created_at <= \$7/)
assert.deepEqual(calls[0]?.params, [
'%皮肤店%',
'91kaquan',
'paid',
'manual_review',
'kuaishou_ct_assisted',
'2026-09-01T00:00:00.000Z',
'2026-09-02T23:59:59.999Z',
])
assert.match(calls[1]?.sql || '', /LIMIT \$8 OFFSET \$9/)
assert.deepEqual(calls[1]?.params, [...(calls[0]?.params || []), 10, 10])
})
test('订单列表保留订单号和精确 SKU 旧筛选参数兼容', async () => {
const calls: Array<{ sql: string; params: unknown[] }> = []
await listOrdersWithExecutor(createExecutor(calls, 0), {
platformOrderId: '26245',
skuCode: 'SKU-1',
})
assert.match(calls[0]?.sql || '', /o\.platform_order_id ILIKE \$1/)
assert.match(calls[0]?.sql || '', /oi\.sku_code = \$2/)
assert.deepEqual(calls[0]?.params, ['%26245%', 'SKU-1'])
})
function createExecutor(
calls: Array<{ sql: string; params: unknown[] }>,
total: number,
): OrderQueryExecutor {
return (async (sql: string, params: unknown[] = []) => {
calls.push({ sql, params: [...params] })
return {
command: 'SELECT',
rowCount: calls.length === 1 ? 1 : 0,
oid: 0,
fields: [],
rows: calls.length === 1 ? [{ total }] : [],
}
}) as OrderQueryExecutor
}
+63 -12
View File
@@ -202,18 +202,57 @@ export async function getOrderById(orderId: number | string): Promise<OrderRow |
}
export async function listOrders({
page = 1,
pageSize = 20,
platformOrderId = '',
payStatus = '',
skuCode = '',
dateFrom = '',
dateTo = '',
...input
}: OrderListQueryInput = {}): Promise<OrderListQueryResult> {
return listOrdersWithExecutor(query, input)
}
export async function listOrdersWithExecutor(
executor: OrderQueryExecutor,
{
page = 1,
pageSize = 20,
keyword = '',
provider = '',
executorKey = '',
platformOrderId = '',
payStatus = '',
taskStatus = '',
skuCode = '',
dateFrom = '',
dateTo = '',
}: OrderListQueryInput = {},
): Promise<OrderListQueryResult> {
const offset = (page - 1) * pageSize
const filters: string[] = []
const params: unknown[] = []
if (keyword) {
params.push(`%${keyword}%`)
const keywordParam = `$${params.length}`
filters.push(`(
(
o.platform_order_id || ' ' || o.shop_id || ' ' || o.shop_name || ' ' ||
o.buyer_id || ' ' || o.buyer_name || ' ' || o.receiver_contact
) ILIKE ${keywordParam}
OR EXISTS (
SELECT 1
FROM order_items oi
WHERE oi.order_id = o.id
AND (
oi.sku_code || ' ' || oi.sku_name || ' ' ||
COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'itemTitle', '') || ' ' ||
COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'skuNick', '')
) ILIKE ${keywordParam}
)
)`)
}
if (provider) {
params.push(provider)
filters.push(`o.provider = $${params.length}`)
}
if (platformOrderId) {
params.push(`%${platformOrderId}%`)
filters.push(`o.platform_order_id ILIKE $${params.length}`)
@@ -224,6 +263,20 @@ export async function listOrders({
filters.push(`o.pay_status = $${params.length}`)
}
if (taskStatus) {
params.push(taskStatus)
filters.push(
`EXISTS (SELECT 1 FROM fulfillment_tasks ft WHERE ft.order_id = o.id AND ft.task_status = $${params.length})`,
)
}
if (executorKey) {
params.push(executorKey)
filters.push(
`EXISTS (SELECT 1 FROM fulfillment_tasks ft WHERE ft.order_id = o.id AND ft.executor_key = $${params.length})`,
)
}
if (skuCode) {
params.push(skuCode)
filters.push(
@@ -242,18 +295,16 @@ export async function listOrders({
}
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
const totalResult = await query<{ [column: string]: unknown; total: number }>(
const totalResult = await executor<{ [column: string]: unknown; total: number }>(
`SELECT COUNT(*)::int AS total FROM orders o ${whereClause}`,
params,
)
params.push(pageSize)
params.push(offset)
const itemsResult = await query<OrderListRow>(
const itemsResult = await executor<OrderListRow>(
`
SELECT
o.*,
(SELECT COUNT(*)::int FROM fulfillment_tasks ft WHERE ft.order_id = o.id) AS task_count
SELECT o.*
FROM orders o
${whereClause}
ORDER BY o.id DESC
@@ -0,0 +1,48 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { buildTaskListFilters } from './task-repo.js'
test('任务列表综合关键词覆盖任务订单商品与角色字段', () => {
const result = buildTaskListFilters({
keyword: '指挥官密钥',
executorKey: 'kuaishou_ct_assisted',
status: 'manual_review',
dateFrom: '2026-09-01T00:00:00.000Z',
dateTo: '2026-09-02T23:59:59.999Z',
})
assert.match(result.whereClause, /ft\.task_no.*ft\.platform_order_id.*ILIKE \$1/s)
assert.match(result.whereClause, /oi\.sku_code ILIKE \$1/)
assert.match(result.whereClause, /oi\.sku_name ILIKE \$1/)
assert.match(result.whereClause, /kuaishouSendCode'.*itemTitle.*ILIKE \$1/s)
assert.match(result.whereClause, /kuaishouSendCode'.*skuNick.*ILIKE \$1/s)
assert.match(result.whereClause, /kcts\.role_id.*ctx\.role_id.*ILIKE \$1/s)
assert.match(result.whereClause, /kcts\.role_name.*ctx\.role_name.*ctx\.nickname.*ILIKE \$1/s)
assert.match(result.whereClause, /ft\.executor_key = \$2/)
assert.match(result.whereClause, /ft\.task_status = \$3/)
assert.match(result.whereClause, /ft\.created_at >= \$4/)
assert.match(result.whereClause, /ft\.created_at <= \$5/)
assert.deepEqual(result.params, [
'%指挥官密钥%',
'kuaishou_ct_assisted',
'manual_review',
'2026-09-01T00:00:00.000Z',
'2026-09-02T23:59:59.999Z',
])
})
test('任务列表保留旧版分字段筛选参数', () => {
const result = buildTaskListFilters({
platformOrderId: '26245',
taskNo: 'DT-1',
skuCode: 'SKU-1',
roleId: '10001',
})
assert.match(result.whereClause, /ft\.platform_order_id ILIKE \$1/)
assert.match(result.whereClause, /ft\.task_no ILIKE \$2/)
assert.match(result.whereClause, /oi\.sku_code = \$3/)
assert.match(result.whereClause, /kcts\.role_id.*ctx\.role_id.*ILIKE \$4/s)
assert.deepEqual(result.params, ['%26245%', '%DT-1%', 'SKU-1', '%10001%'])
})
+72 -28
View File
@@ -79,6 +79,19 @@ export async function listTasksByOrderId(orderId: number | string): Promise<Task
return result.rows
}
export async function listTasksByOrderIds(orderIds: number[]): Promise<TaskRow[]> {
const normalizedIds = orderIds.map(Number).filter((value) => value > 0)
if (normalizedIds.length === 0) return []
const result = await query<TaskRow>(
`${buildTaskSelect()}
WHERE ft.order_id = ANY($1::bigint[])
ORDER BY ft.order_id ASC, ft.id ASC`,
[normalizedIds],
)
return result.rows
}
export async function createTask(input: TaskCreateInput): Promise<TaskRow | null> {
return withTransaction(async (client) => {
const taskResult = await client.query<{ id: number }>(
@@ -418,6 +431,43 @@ export async function findAffiliateDashTaskByOrder(input: {
export async function listTasks({
page = 1,
pageSize = 20,
...filters
}: TaskListQueryInput = {}): Promise<TaskListQueryResult> {
const offset = (page - 1) * pageSize
const { whereClause, params } = buildTaskListFilters(filters)
const totalResult = await query<{ [column: string]: unknown; total: number }>(
`
SELECT COUNT(*)::int AS total
FROM fulfillment_tasks ft
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
LEFT JOIN task_runtime_contexts ctx ON ctx.task_id = ft.id
LEFT JOIN kuaishou_cloud_task_states kcts ON kcts.task_id = ft.id
${whereClause}
`,
params,
)
params.push(pageSize)
params.push(offset)
const itemsResult = await query<TaskRow>(
`${buildTaskSelect('oi.sku_code, oi.sku_name, oi.quantity')}
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
${whereClause}
ORDER BY ft.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}`,
params,
)
return {
items: itemsResult.rows,
total: Number(totalResult.rows[0]?.total || 0),
}
}
export function buildTaskListFilters({
keyword = '',
executorKey = '',
status = '',
platformOrderId = '',
taskNo = '',
@@ -425,11 +475,29 @@ export async function listTasks({
roleId = '',
dateFrom = '',
dateTo = '',
}: TaskListQueryInput = {}): Promise<TaskListQueryResult> {
const offset = (page - 1) * pageSize
}: TaskListQueryInput = {}): { whereClause: string; params: unknown[] } {
const filters: string[] = []
const params: unknown[] = []
if (keyword) {
params.push(`%${keyword}%`)
const keywordParam = `$${params.length}`
filters.push(`(
(ft.task_no || ' ' || ft.platform_order_id) ILIKE ${keywordParam}
OR oi.sku_code ILIKE ${keywordParam}
OR oi.sku_name ILIKE ${keywordParam}
OR COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'itemTitle', '') ILIKE ${keywordParam}
OR COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'skuNick', '') ILIKE ${keywordParam}
OR COALESCE(NULLIF(kcts.role_id, ''), ctx.role_id, '') ILIKE ${keywordParam}
OR COALESCE(NULLIF(kcts.role_name, ''), NULLIF(ctx.role_name, ''), ctx.nickname, '') ILIKE ${keywordParam}
)`)
}
if (executorKey) {
params.push(executorKey)
filters.push(`ft.executor_key = $${params.length}`)
}
if (status) {
params.push(status)
filters.push(`ft.task_status = $${params.length}`)
@@ -465,33 +533,9 @@ export async function listTasks({
filters.push(`ft.created_at <= $${params.length}`)
}
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
const totalResult = await query<{ [column: string]: unknown; total: number }>(
`
SELECT COUNT(*)::int AS total
FROM fulfillment_tasks ft
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
LEFT JOIN task_runtime_contexts ctx ON ctx.task_id = ft.id
LEFT JOIN kuaishou_cloud_task_states kcts ON kcts.task_id = ft.id
${whereClause}
`,
params,
)
params.push(pageSize)
params.push(offset)
const itemsResult = await query<TaskRow>(
`${buildTaskSelect('oi.sku_code, oi.sku_name, oi.quantity')}
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
${whereClause}
ORDER BY ft.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}`,
params,
)
return {
items: itemsResult.rows,
total: Number(totalResult.rows[0]?.total || 0),
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
}
}