优化订单履约分流与后台筛选
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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%'])
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user