Merge branch 'main' into dev
This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { syncKuaishouCloudTaskStateForTask } from './kuaishou-cloud-task-state-repo.js'
|
||||
|
||||
test('syncKuaishouCloudTaskStateForTask 将 Date 更新时间规范化为 ISO 字符串', async () => {
|
||||
const calls: Array<{ text: string; params?: unknown[] }> = []
|
||||
const executor = async (text: string, params?: unknown[]) => {
|
||||
calls.push({ text, params })
|
||||
return { rows: [] }
|
||||
}
|
||||
|
||||
await syncKuaishouCloudTaskStateForTask(
|
||||
{
|
||||
taskId: 9,
|
||||
executorKey: 'kuaishou_ct_assisted',
|
||||
contextJson: {
|
||||
kuaishouCloudFulfillment: {
|
||||
ticket: {
|
||||
status: 'verified',
|
||||
code: '597172E91CF18417',
|
||||
},
|
||||
binding: {
|
||||
bindUrl: 'https://example.test/bind',
|
||||
vnPhone: '14062706492',
|
||||
roleName: '测试角色',
|
||||
roleId: '4212063544',
|
||||
resolvedSourceKey: 'account_test',
|
||||
},
|
||||
dispatch: {
|
||||
status: 'pending',
|
||||
},
|
||||
consume: {
|
||||
status: 'pending',
|
||||
},
|
||||
},
|
||||
},
|
||||
updatedAt: new Date('2026-05-29T09:05:46.658Z'),
|
||||
},
|
||||
executor,
|
||||
)
|
||||
|
||||
assert.equal(calls.length, 1)
|
||||
assert.match(calls[0].text, /INSERT INTO kuaishou_cloud_task_states/)
|
||||
assert.equal(calls[0].params?.[10], '2026-05-29T09:05:46.658Z')
|
||||
})
|
||||
@@ -1,180 +0,0 @@
|
||||
import { query } from '../db/client.js'
|
||||
import { TASK_STATUS } from '../domain/task-status.js'
|
||||
import { maskCode } from '../utils/masking.js'
|
||||
import { parseTaskContext } from '../utils/task-json.js'
|
||||
import { normalizeTimestampIso } from '../utils/time.js'
|
||||
import type { JsonRecord } from '../types/json.js'
|
||||
|
||||
type QueryExecutor = (text: string, params?: unknown[]) => Promise<{ rows: unknown[] }>
|
||||
export type KuaishouCloudSourceLoadStat = {
|
||||
sourceKey: string
|
||||
activeCount: number
|
||||
redeemingCount: number
|
||||
}
|
||||
|
||||
export type KuaishouCloudTaskStateSyncInput = {
|
||||
taskId: number | string
|
||||
executorKey?: unknown
|
||||
contextJson?: unknown
|
||||
updatedAt?: unknown
|
||||
}
|
||||
|
||||
export async function syncKuaishouCloudTaskStateForTask(
|
||||
input: KuaishouCloudTaskStateSyncInput,
|
||||
executor: QueryExecutor = query,
|
||||
): Promise<void> {
|
||||
const taskId = Number(input.taskId || 0)
|
||||
if (!Number.isFinite(taskId) || taskId <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (String(input.executorKey || '').trim() !== 'kuaishou_ct_assisted') {
|
||||
await executor('DELETE FROM kuaishou_cloud_task_states WHERE task_id = $1', [taskId])
|
||||
return
|
||||
}
|
||||
|
||||
const flow = resolveKuaishouCloudFlow(input.contextJson)
|
||||
if (!flow) {
|
||||
await executor('DELETE FROM kuaishou_cloud_task_states WHERE task_id = $1', [taskId])
|
||||
return
|
||||
}
|
||||
|
||||
const ticket = asRecord(flow.ticket)
|
||||
const binding = asRecord(flow.binding)
|
||||
const role = asRecord(flow.role)
|
||||
const dispatch = asRecord(flow.dispatch)
|
||||
const consume = asRecord(flow.consume)
|
||||
const roleName = pickFirstNonEmpty([role.name, binding.roleName])
|
||||
const roleId = pickFirstNonEmpty([role.rid, binding.roleId])
|
||||
const updatedAt = normalizeTimestampIso(input.updatedAt)
|
||||
|
||||
await executor(
|
||||
`
|
||||
INSERT INTO kuaishou_cloud_task_states (
|
||||
task_id,
|
||||
ticket_status,
|
||||
ticket_code_masked,
|
||||
bind_url,
|
||||
vn_phone,
|
||||
role_name,
|
||||
role_id,
|
||||
dispatch_status,
|
||||
consume_status,
|
||||
source_key,
|
||||
updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
ON CONFLICT (task_id) DO UPDATE
|
||||
SET
|
||||
ticket_status = EXCLUDED.ticket_status,
|
||||
ticket_code_masked = EXCLUDED.ticket_code_masked,
|
||||
bind_url = EXCLUDED.bind_url,
|
||||
vn_phone = EXCLUDED.vn_phone,
|
||||
role_name = EXCLUDED.role_name,
|
||||
role_id = EXCLUDED.role_id,
|
||||
dispatch_status = EXCLUDED.dispatch_status,
|
||||
consume_status = EXCLUDED.consume_status,
|
||||
source_key = EXCLUDED.source_key,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
`,
|
||||
[
|
||||
taskId,
|
||||
normalizeStatus(ticket.status),
|
||||
maskCode(ticket.code),
|
||||
String(binding.bindUrl || '').trim(),
|
||||
String(binding.vnPhone || '').trim(),
|
||||
roleName,
|
||||
roleId,
|
||||
normalizeStatus(dispatch.status),
|
||||
normalizeStatus(consume.status),
|
||||
String(binding.resolvedSourceKey || '').trim(),
|
||||
updatedAt,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export async function listKuaishouCloudSourceLoadStats(
|
||||
sourceKeys: unknown[] = [],
|
||||
executor: QueryExecutor = query,
|
||||
): Promise<KuaishouCloudSourceLoadStat[]> {
|
||||
const candidates = normalizeStringArray(sourceKeys)
|
||||
if (candidates.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const activeStatuses = [
|
||||
TASK_STATUS.WAITING_BINDING,
|
||||
TASK_STATUS.ROLE_CONFIRMED,
|
||||
TASK_STATUS.REDEEMING,
|
||||
TASK_STATUS.DISPATCHED_PENDING_RETURN,
|
||||
]
|
||||
const result = await executor(
|
||||
`
|
||||
SELECT
|
||||
kcts.source_key,
|
||||
COUNT(*) FILTER (WHERE ft.task_status = ANY($2::text[])) AS active_count,
|
||||
COUNT(*) FILTER (WHERE ft.task_status = $3) AS redeeming_count
|
||||
FROM fulfillment_tasks ft
|
||||
JOIN kuaishou_cloud_task_states kcts ON kcts.task_id = ft.id
|
||||
WHERE ft.executor_key = 'kuaishou_ct_assisted'
|
||||
AND kcts.source_key = ANY($1::text[])
|
||||
AND ft.task_status = ANY($2::text[])
|
||||
GROUP BY kcts.source_key
|
||||
`,
|
||||
[candidates, activeStatuses, TASK_STATUS.REDEEMING],
|
||||
)
|
||||
|
||||
return result.rows
|
||||
.map((row) => {
|
||||
const source = asRecord(row)
|
||||
return {
|
||||
sourceKey: String(source.source_key || '').trim(),
|
||||
activeCount: Number(source.active_count || 0) || 0,
|
||||
redeemingCount: Number(source.redeeming_count || 0) || 0,
|
||||
}
|
||||
})
|
||||
.filter((row) => row.sourceKey)
|
||||
}
|
||||
|
||||
function resolveKuaishouCloudFlow(contextJson: unknown): JsonRecord | null {
|
||||
const context = parseTaskContext({ context_json: contextJson })
|
||||
const flow = asRecord(context.kuaishouCloudFulfillment)
|
||||
return Object.keys(flow).length > 0 ? flow : null
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonRecord) : {}
|
||||
}
|
||||
|
||||
function normalizeStatus(value: unknown): string {
|
||||
return String(value || 'pending').trim() || 'pending'
|
||||
}
|
||||
|
||||
function normalizeStringArray(value: unknown): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return Array.from(new Set(value.map((item) => String(item || '').trim()).filter(Boolean)))
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return Array.from(
|
||||
new Set(
|
||||
value
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function pickFirstNonEmpty(values: unknown[]): string {
|
||||
for (const value of values) {
|
||||
const normalized = String(value || '').trim()
|
||||
if (normalized) {
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
@@ -12,11 +12,11 @@ test('订单列表综合筛选下推到总数和分页查询', async () => {
|
||||
pageSize: 10,
|
||||
keyword: '皮肤店',
|
||||
provider: '91kaquan',
|
||||
executorKey: 'kuaishou_ct_assisted',
|
||||
executorKey: 'affiliate_dash',
|
||||
payStatus: 'paid',
|
||||
taskStatus: 'manual_review',
|
||||
dateFrom: '2026-09-01T00:00:00.000Z',
|
||||
dateTo: '2026-09-02T23:59:59.999Z',
|
||||
dateFrom: '2026-09-01',
|
||||
dateTo: '2026-09-02',
|
||||
})
|
||||
|
||||
assert.equal(result.total, 3)
|
||||
@@ -24,25 +24,37 @@ test('订单列表综合筛选下推到总数和分页查询', async () => {
|
||||
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.match(calls[0]?.sql || '', /o\.provider IN \('91kaquan', 'open_91', '91'\)/)
|
||||
assert.match(calls[0]?.sql || '', /o\.pay_status = \$2/)
|
||||
assert.match(calls[0]?.sql || '', /ft\.task_status = \$3/)
|
||||
assert.match(calls[0]?.sql || '', /ft\.executor_key = \$4/)
|
||||
// 有关键词时不应用日期过滤
|
||||
assert.doesNotMatch(calls[0]?.sql || '', /paid_at/i)
|
||||
assert.deepEqual(calls[0]?.params, ['%皮肤店%', 'paid', 'manual_review', 'affiliate_dash'])
|
||||
assert.match(calls[1]?.sql || '', /LIMIT \$5 OFFSET \$6/)
|
||||
assert.deepEqual(calls[1]?.params, [...(calls[0]?.params || []), 10, 10])
|
||||
})
|
||||
|
||||
test('订单列表无关键词时按支付时间与中国时区日期筛选', async () => {
|
||||
const calls: Array<{ sql: string; params: unknown[] }> = []
|
||||
|
||||
await listOrdersWithExecutor(createExecutor(calls, 0), {
|
||||
dateFrom: '2026-09-01',
|
||||
dateTo: '2026-09-07',
|
||||
})
|
||||
|
||||
assert.match(
|
||||
calls[0]?.sql || '',
|
||||
/COALESCE\(o\.paid_at, o\.created_at\) >= \(\$1::date\)::timestamp AT TIME ZONE 'Asia\/Shanghai'/,
|
||||
)
|
||||
assert.match(
|
||||
calls[0]?.sql || '',
|
||||
/COALESCE\(o\.paid_at, o\.created_at\) < \(\(\$2::date \+ 1\)::timestamp AT TIME ZONE 'Asia\/Shanghai'\)/,
|
||||
)
|
||||
assert.deepEqual(calls[0]?.params, ['2026-09-01', '2026-09-07'])
|
||||
assert.match(calls[1]?.sql || '', /ORDER BY COALESCE\(o\.paid_at, o\.created_at\) DESC/)
|
||||
})
|
||||
|
||||
test('订单列表保留订单号和精确 SKU 旧筛选参数兼容', async () => {
|
||||
const calls: Array<{ sql: string; params: unknown[] }> = []
|
||||
|
||||
@@ -56,6 +68,38 @@ test('订单列表保留订单号和精确 SKU 旧筛选参数兼容', async ()
|
||||
assert.deepEqual(calls[0]?.params, ['%26245%', 'SKU-1'])
|
||||
})
|
||||
|
||||
test('订单列表关键词为拼多多 order_sn 时走平台订单号索引快路径', async () => {
|
||||
const calls: Array<{ sql: string; params: unknown[] }> = []
|
||||
const orderSn = '260915-091435834962130'
|
||||
|
||||
await listOrdersWithExecutor(createExecutor(calls, 0), {
|
||||
keyword: orderSn,
|
||||
})
|
||||
|
||||
assert.match(
|
||||
calls[0]?.sql || '',
|
||||
/o\.platform_order_id = \$1 OR o\.platform_order_id LIKE \$1 \|\| '%'/s,
|
||||
)
|
||||
assert.doesNotMatch(calls[0]?.sql || '', /oi\.sku_code/i)
|
||||
assert.doesNotMatch(calls[0]?.sql || '', /item_snapshot_json/i)
|
||||
assert.deepEqual(calls[0]?.params, [orderSn])
|
||||
})
|
||||
|
||||
test('订单列表 platformOrderId 为拼多多 order_sn 时走索引快路径', async () => {
|
||||
const calls: Array<{ sql: string; params: unknown[] }> = []
|
||||
const orderSn = '260915-091435834962130'
|
||||
|
||||
await listOrdersWithExecutor(createExecutor(calls, 0), {
|
||||
platformOrderId: orderSn,
|
||||
})
|
||||
|
||||
assert.match(
|
||||
calls[0]?.sql || '',
|
||||
/o\.platform_order_id = \$1 OR o\.platform_order_id LIKE \$1 \|\| '%'/s,
|
||||
)
|
||||
assert.deepEqual(calls[0]?.params, [orderSn])
|
||||
})
|
||||
|
||||
function createExecutor(
|
||||
calls: Array<{ sql: string; params: unknown[] }>,
|
||||
total: number,
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
OrderUpdateInput,
|
||||
} from '../types/repository/inputs.js'
|
||||
import type { OrderListQueryResult, OrderListRow, OrderRow } from '../types/repository/rows.js'
|
||||
import { isPlatformOrderIdLookupKeyword } from '../utils/platform-order-id.js'
|
||||
|
||||
export type OrderQueryExecutor = <T extends QueryResultRow = QueryResultRow>(
|
||||
text: string,
|
||||
@@ -251,9 +252,9 @@ export async function listOrdersWithExecutor(
|
||||
|
||||
if (keyword) {
|
||||
const trimmedKeyword = String(keyword).trim()
|
||||
const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword)
|
||||
const isOrderNoLookup = isPlatformOrderIdLookupKeyword(trimmedKeyword)
|
||||
|
||||
if (isPureOrderNo) {
|
||||
if (isOrderNoLookup) {
|
||||
params.push(trimmedKeyword)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
@@ -294,13 +295,21 @@ export async function listOrdersWithExecutor(
|
||||
}
|
||||
|
||||
if (provider) {
|
||||
params.push(provider)
|
||||
filters.push(`o.provider = $${params.length}`)
|
||||
appendProviderFilter(filters, params, provider)
|
||||
}
|
||||
|
||||
if (platformOrderId) {
|
||||
params.push(`%${platformOrderId}%`)
|
||||
filters.push(`o.platform_order_id ILIKE $${params.length}`)
|
||||
const trimmedPlatformOrderId = String(platformOrderId).trim()
|
||||
if (isPlatformOrderIdLookupKeyword(trimmedPlatformOrderId)) {
|
||||
params.push(trimmedPlatformOrderId)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
`(o.platform_order_id = ${paramIndex} OR o.platform_order_id LIKE ${paramIndex} || '%')`,
|
||||
)
|
||||
} else {
|
||||
params.push(`%${trimmedPlatformOrderId}%`)
|
||||
filters.push(`o.platform_order_id ILIKE $${params.length}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (payStatus) {
|
||||
@@ -329,14 +338,20 @@ export async function listOrdersWithExecutor(
|
||||
)
|
||||
}
|
||||
|
||||
if (dateFrom) {
|
||||
params.push(dateFrom)
|
||||
filters.push(`o.created_at >= $${params.length}`)
|
||||
// 有关键词时不限日期,避免默认近 N 天挡住搜单号/商品。
|
||||
const applyDateFilter = Boolean(dateFrom || dateTo) && !String(keyword || '').trim()
|
||||
if (applyDateFilter && dateFrom) {
|
||||
params.push(String(dateFrom).trim().slice(0, 10))
|
||||
filters.push(
|
||||
`COALESCE(o.paid_at, o.created_at) >= ($${params.length}::date)::timestamp AT TIME ZONE 'Asia/Shanghai'`,
|
||||
)
|
||||
}
|
||||
|
||||
if (dateTo) {
|
||||
params.push(dateTo)
|
||||
filters.push(`o.created_at <= $${params.length}`)
|
||||
if (applyDateFilter && dateTo) {
|
||||
params.push(String(dateTo).trim().slice(0, 10))
|
||||
filters.push(
|
||||
`COALESCE(o.paid_at, o.created_at) < (($${params.length}::date + 1)::timestamp AT TIME ZONE 'Asia/Shanghai')`,
|
||||
)
|
||||
}
|
||||
|
||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||
@@ -352,7 +367,7 @@ export async function listOrdersWithExecutor(
|
||||
SELECT o.*
|
||||
FROM orders o
|
||||
${whereClause}
|
||||
ORDER BY o.id DESC
|
||||
ORDER BY COALESCE(o.paid_at, o.created_at) DESC NULLS LAST, o.id DESC
|
||||
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||
`,
|
||||
params,
|
||||
@@ -363,3 +378,33 @@ export async function listOrdersWithExecutor(
|
||||
total: Number(totalResult.rows[0]?.total || 0),
|
||||
}
|
||||
}
|
||||
|
||||
/** 来源渠道筛选:兼容历史 provider / platform 多种写法。 */
|
||||
function appendProviderFilter(filters: string[], params: unknown[], provider: string) {
|
||||
const normalized = String(provider || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (!normalized) {
|
||||
return
|
||||
}
|
||||
|
||||
if (normalized === 'pdd') {
|
||||
filters.push(`(o.provider = 'pdd' OR o.platform = 'pdd')`)
|
||||
return
|
||||
}
|
||||
|
||||
if (normalized === '91' || normalized === '91kaquan' || normalized === 'open_91') {
|
||||
filters.push(
|
||||
`(o.provider IN ('91kaquan', 'open_91', '91') OR o.platform IN ('91kaquan', '91'))`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (normalized === 'kuaishou' || normalized === 'kuaishou-industry') {
|
||||
filters.push(`(o.provider LIKE 'kuaishou%' OR o.platform LIKE 'kuaishou%')`)
|
||||
return
|
||||
}
|
||||
|
||||
params.push(provider)
|
||||
filters.push(`(o.provider = $${params.length} OR o.platform = $${params.length})`)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { buildTaskListFilters } from './task-repo.js'
|
||||
test('任务列表综合关键词覆盖任务订单商品与角色字段', () => {
|
||||
const result = buildTaskListFilters({
|
||||
keyword: '指挥官密钥',
|
||||
executorKey: 'kuaishou_ct_assisted',
|
||||
executorKey: 'affiliate_dash',
|
||||
status: 'manual_review',
|
||||
dateFrom: '2026-09-01T00:00:00.000Z',
|
||||
dateTo: '2026-09-02T23:59:59.999Z',
|
||||
@@ -25,7 +25,7 @@ test('任务列表综合关键词覆盖任务订单商品与角色字段', () =>
|
||||
assert.match(result.whereClause, /ft\.created_at <= \$5/)
|
||||
assert.deepEqual(result.params, [
|
||||
'%指挥官密钥%',
|
||||
'kuaishou_ct_assisted',
|
||||
'affiliate_dash',
|
||||
'manual_review',
|
||||
'2026-09-01T00:00:00.000Z',
|
||||
'2026-09-02T23:59:59.999Z',
|
||||
@@ -62,6 +62,20 @@ test('任务列表关键词为纯数字时智能路由到平台订单号索引
|
||||
assert.deepEqual(result.params, ['20247230034021572'])
|
||||
})
|
||||
|
||||
test('任务列表关键词为拼多多 order_sn 时智能路由到平台订单号索引条件', () => {
|
||||
const result = buildTaskListFilters({
|
||||
keyword: '260915-091435834962130',
|
||||
})
|
||||
|
||||
assert.match(
|
||||
result.whereClause,
|
||||
/ft\.platform_order_id = \$1 OR ft\.platform_order_id LIKE \$1 \|\| '%'/s,
|
||||
)
|
||||
assert.doesNotMatch(result.whereClause, /oi\.sku_code/i)
|
||||
assert.doesNotMatch(result.whereClause, /item_snapshot_json/i)
|
||||
assert.deepEqual(result.params, ['260915-091435834962130'])
|
||||
})
|
||||
|
||||
test('任务列表关键词为 DT 任务号时智能路由到任务号索引条件', () => {
|
||||
const result = buildTaskListFilters({
|
||||
keyword: 'DT881aa66294cc',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { query, withTransaction } from '../db/client.js'
|
||||
import type { PoolClient } from 'pg'
|
||||
|
||||
import { syncKuaishouCloudTaskStateForTask } from './kuaishou-cloud-task-state-repo.js'
|
||||
import { isPlatformOrderIdLookupKeyword } from '../utils/platform-order-id.js'
|
||||
import { normalizeTimestampIso } from '../utils/time.js'
|
||||
import type {
|
||||
TaskCreateInput,
|
||||
@@ -12,7 +12,6 @@ import type {
|
||||
import type { TaskListQueryResult, TaskRow } from '../types/repository/rows.js'
|
||||
|
||||
type TaskQueryExecutor = (text: string, params?: unknown[]) => Promise<{ rows: unknown[] }>
|
||||
const KUAISHOU_FEIFEI_EXECUTOR_KEY = 'kuaishou_feifei'
|
||||
const AFFILIATE_DASH_EXECUTOR_KEY = 'affiliate_dash'
|
||||
|
||||
type TaskRuntimeContextRow = {
|
||||
@@ -168,16 +167,6 @@ export async function createTask(input: TaskCreateInput): Promise<TaskRow | null
|
||||
)
|
||||
}
|
||||
|
||||
await syncKuaishouCloudTaskStateForTask(
|
||||
{
|
||||
taskId,
|
||||
executorKey: input.executorKey,
|
||||
contextJson: input.contextJson || '{}',
|
||||
updatedAt: input.updatedAt,
|
||||
},
|
||||
client.query.bind(client) as TaskQueryExecutor,
|
||||
)
|
||||
|
||||
return getTaskByIdWithExecutor(client.query.bind(client) as TaskQueryExecutor, taskId)
|
||||
})
|
||||
}
|
||||
@@ -262,16 +251,6 @@ export async function updateTask(
|
||||
)
|
||||
}
|
||||
|
||||
await syncKuaishouCloudTaskStateForTask(
|
||||
{
|
||||
taskId,
|
||||
executorKey: next.executor_key,
|
||||
contextJson: next.context_json,
|
||||
updatedAt: next.updated_at,
|
||||
},
|
||||
client.query.bind(client) as TaskQueryExecutor,
|
||||
)
|
||||
|
||||
return getTaskByIdWithExecutor(client.query.bind(client) as TaskQueryExecutor, taskId)
|
||||
})
|
||||
}
|
||||
@@ -329,16 +308,6 @@ export async function updateTaskStatusIfCurrent(
|
||||
return null
|
||||
}
|
||||
|
||||
await syncKuaishouCloudTaskStateForTask(
|
||||
{
|
||||
taskId: updated.id,
|
||||
executorKey: updated.executor_key,
|
||||
contextJson: updated.context_json,
|
||||
updatedAt: updated.updated_at,
|
||||
},
|
||||
client.query.bind(client) as TaskQueryExecutor,
|
||||
)
|
||||
|
||||
return getTaskByIdWithExecutor(client.query.bind(client) as TaskQueryExecutor, taskId)
|
||||
})
|
||||
}
|
||||
@@ -360,6 +329,8 @@ export async function findTaskByClaimTokenId(
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
const KUAISHOU_FEIFEI_EXECUTOR_KEY = 'kuaishou_feifei'
|
||||
|
||||
export async function findKuaishouFeifeiTaskByOrder(input: {
|
||||
platformOrderNo?: unknown
|
||||
orderNo?: unknown
|
||||
@@ -501,10 +472,10 @@ export function buildTaskListFilters({
|
||||
|
||||
if (keyword) {
|
||||
const trimmedKeyword = String(keyword).trim()
|
||||
const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword)
|
||||
const isOrderNoLookup = isPlatformOrderIdLookupKeyword(trimmedKeyword)
|
||||
const isTaskNo = /^DT[a-zA-Z0-9_-]+$/i.test(trimmedKeyword)
|
||||
|
||||
if (isPureOrderNo) {
|
||||
if (isOrderNoLookup) {
|
||||
params.push(trimmedKeyword)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
@@ -540,8 +511,17 @@ export function buildTaskListFilters({
|
||||
}
|
||||
|
||||
if (platformOrderId) {
|
||||
params.push(`%${platformOrderId}%`)
|
||||
filters.push(`ft.platform_order_id ILIKE $${params.length}`)
|
||||
const trimmedPlatformOrderId = String(platformOrderId).trim()
|
||||
if (isPlatformOrderIdLookupKeyword(trimmedPlatformOrderId)) {
|
||||
params.push(trimmedPlatformOrderId)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
`(ft.platform_order_id = ${paramIndex} OR ft.platform_order_id LIKE ${paramIndex} || '%')`,
|
||||
)
|
||||
} else {
|
||||
params.push(`%${trimmedPlatformOrderId}%`)
|
||||
filters.push(`ft.platform_order_id ILIKE $${params.length}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (taskNo) {
|
||||
|
||||
@@ -231,6 +231,27 @@ export async function createWorkProductMatchLog(input: {
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
/** 同一订单项最近一条匹配日志(用于同步去重,避免旧单反复写入)。 */
|
||||
export async function getLatestWorkProductMatchLogByOrderItemId(
|
||||
orderItemId: number | string,
|
||||
): Promise<WorkProductMatchLogRow | null> {
|
||||
const id = Number(orderItemId)
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return null
|
||||
}
|
||||
const result = await query<WorkProductMatchLogRow>(
|
||||
`
|
||||
SELECT *
|
||||
FROM work_product_match_logs
|
||||
WHERE order_item_id = $1
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
`,
|
||||
[id],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
/** 匹配日志分页查询:表本身全量保留,可按结果状态、关键词(商品/SKU/店铺)与日期范围筛选。 */
|
||||
export async function listWorkProductMatchLogs({
|
||||
page = 1,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
normalizeWorkOrderAcceptanceMode,
|
||||
WORK_ORDER_GIFT_PHASE,
|
||||
} from '../../domain/work-order-acceptance-mode.js'
|
||||
import { isPlatformOrderIdLookupKeyword } from '../../utils/platform-order-id.js'
|
||||
export const WORK_ORDER_SELECT = `
|
||||
SELECT
|
||||
wo.*,
|
||||
@@ -819,8 +820,7 @@ export function buildWorkOrderWhere({
|
||||
filters.push(`wo.id = $${params.length}`)
|
||||
}
|
||||
if (normalizedKeyword) {
|
||||
const isPureOrderNo = /^\d{6,}$/.test(normalizedKeyword)
|
||||
if (isPureOrderNo) {
|
||||
if (isPlatformOrderIdLookupKeyword(normalizedKeyword)) {
|
||||
params.push(normalizedKeyword)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
|
||||
Reference in New Issue
Block a user