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

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
@@ -19,6 +19,7 @@ import type { JsonObject } from '../../types/json.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import { randomId } from '../../utils/random.js'
import { nowIso } from '../../utils/time.js'
import { logInfo, logWarn } from '../../utils/logger.js'
import { safeParseJson } from '../admin/admin-query-utils.js'
import { createWorkOrderMaterialAdminNotification } from '../admin/admin-notification-service.js'
import {
@@ -44,6 +45,7 @@ export async function syncWorkerOrdersForSourceOrder(
const productMatchConfig = getWorkerProductMatchConfig()
const created: WorkOrderRow[] = []
const skipped: Array<{ orderItemId: number; reason: string }> = []
const decisions: Array<Record<string, unknown>> = []
for (const item of orderItems) {
const match = resolveMatchingProductRuleDecision(order, item, rules, mappings, {
@@ -75,19 +77,44 @@ export async function syncWorkerOrdersForSourceOrder(
})
}
if (!match.rule) {
const reason = match.reason === 'ambiguous' ? 'rule_match_ambiguous' : 'rule_not_matched'
skipped.push({
orderItemId: Number(item.id),
reason: match.reason === 'ambiguous' ? 'rule_match_ambiguous' : 'rule_not_matched',
reason,
})
decisions.push(
buildWorkerConsumerDecision(item, {
result: 'skipped',
reason,
matchReason: match.reason,
candidates: match.candidates,
}),
)
continue
}
const rule = match.rule
if (options.autoOnly && !rule.auto_create) {
skipped.push({ orderItemId: Number(item.id), reason: 'auto_create_disabled' })
decisions.push(
buildWorkerConsumerDecision(item, {
result: 'skipped',
reason: 'auto_create_disabled',
ruleId: rule.id,
ruleKey: rule.rule_key,
}),
)
continue
}
if (await getWorkOrderByOrderItemId(item.id)) {
skipped.push({ orderItemId: Number(item.id), reason: 'already_exists' })
decisions.push(
buildWorkerConsumerDecision(item, {
result: 'existing',
reason: 'already_exists',
ruleId: rule.id,
ruleKey: rule.rule_key,
}),
)
continue
}
// 订单号跨订单唯一:同一订单的多个 SKU 件允许共享订单号(按件建单是既有设计),
@@ -100,6 +127,14 @@ export async function syncWorkerOrdersForSourceOrder(
})
) {
skipped.push({ orderItemId: Number(item.id), reason: 'platform_order_exists' })
decisions.push(
buildWorkerConsumerDecision(item, {
result: 'skipped',
reason: 'platform_order_exists',
ruleId: rule.id,
ruleKey: rule.rule_key,
}),
)
continue
}
@@ -161,9 +196,31 @@ export async function syncWorkerOrdersForSourceOrder(
requirementJson: JSON.stringify({ fields }),
now,
})
if (!workOrder) continue
if (!workOrder) {
skipped.push({ orderItemId: Number(item.id), reason: 'create_returned_empty' })
decisions.push(
buildWorkerConsumerDecision(item, {
result: 'failed',
reason: 'create_returned_empty',
ruleId: rule.id,
ruleKey: rule.rule_key,
}),
)
continue
}
created.push(workOrder)
decisions.push(
buildWorkerConsumerDecision(item, {
result: 'created',
reason: 'rule_matched',
ruleId: rule.id,
ruleKey: rule.rule_key,
workOrderId: workOrder.id,
workOrderNo: workOrder.work_order_no,
workOrderStatus: workOrder.status,
}),
)
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'system',
@@ -187,10 +244,54 @@ export async function syncWorkerOrdersForSourceOrder(
}
}
logInfo('[worker-order-consumer]', '发单平台消费者处理完成', {
source: options.source || 'source_order',
orderId: order.id,
platformOrderId: order.platform_order_id,
orderItemCount: orderItems.length,
createdCount: created.length,
skippedCount: skipped.length,
decisions,
})
const failedDecisions = decisions.filter((decision) => decision.result === 'failed')
if (failedDecisions.length > 0) {
logWarn('[worker-order-consumer]', '发单平台消费者存在未成功落库的订单项', {
source: options.source || 'source_order',
orderId: order.id,
platformOrderId: order.platform_order_id,
decisions: failedDecisions,
})
}
return {
created: created.map((workOrder) => mapWorkOrderAdmin(workOrder)),
skipped,
createdCount: created.length,
skippedCount: skipped.length,
decisions,
}
}
function buildWorkerConsumerDecision(
item: OrderItemRow,
detail: Record<string, unknown>,
): Record<string, unknown> {
const snapshot = safeParseJson(item.item_snapshot_json)
const sendCode =
snapshot.kuaishouSendCode &&
typeof snapshot.kuaishouSendCode === 'object' &&
!Array.isArray(snapshot.kuaishouSendCode)
? (snapshot.kuaishouSendCode as JsonObject)
: {}
return {
orderItemId: Number(item.id),
skuCode: item.sku_code,
skuName: item.sku_name,
quantity: Number(item.quantity || 1),
sourceItemTitle: String(sendCode.itemTitle || '').trim(),
sourceSkuNick: String(sendCode.skuNick || '').trim(),
...detail,
}
}