feat(fulfillment): 渠道联动与管道兜底

- 人工配置保存/删除同步渠道规则,人工确认写 channel/status
- 按 SKU 指定渠道默认同时落规则
- 管道补充:载体缺失、人工成本未记录、事实与总账按水位追赶
This commit is contained in:
yml2213
2026-10-31 19:52:22 +08:00
parent 4272515956
commit 0ce3c8fa97
6 changed files with 189 additions and 12 deletions
@@ -96,7 +96,11 @@ export async function listManualFulfillmentPending(
const keyword = String(input.keyword || '').trim()
const status = String(input.status || '').trim()
const conditions = ["oi.fulfillment_mode = 'manual'", 'cfg.id IS NOT NULL']
const conditions = [
"oi.fulfillment_status = 'assigned'",
"oi.fulfillment_channel = 'manual'",
'cfg.id IS NOT NULL',
]
const params: unknown[] = []
if (status === 'pending') {
@@ -219,7 +223,7 @@ export async function confirmManualFulfillment(payload: JsonObject, session: Adm
COALESCE(oi.revenue_amount_fen, 0)::bigint AS revenue_fen,
COALESCE(o.paid_at, o.created_at) AS basis_at,
oi.canonical_product_id,
oi.fulfillment_mode,
COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) AS fulfillment_mode,
oi.fulfillment_config_id AS config_id,
COALESCE(cfg.platform_key, '') AS platform_key,
COALESCE(cfg.credit_quantity, 0)::bigint AS credit_quantity,
@@ -450,6 +454,21 @@ export async function confirmManualFulfillment(payload: JsonObject, session: Adm
})
}
// 人工确认即渠道成立:写新三列,避免订单一直停在 pending。
await client.query(
`
UPDATE order_items
SET fulfillment_channel = 'manual',
fulfillment_status = 'assigned',
fulfillment_source = 'manual_confirm',
fulfillment_decided_at = $2,
updated_at = $2
WHERE id = $1
AND fulfillment_status <> 'archived'
`,
[orderItemId, now],
)
// 人工缺成本异常立即收口,不等下一轮定时任务。
await client.query(
`UPDATE cost_exceptions
@@ -461,6 +480,20 @@ export async function confirmManualFulfillment(payload: JsonObject, session: Adm
[orderItemId, now],
)
// v2 聚合异常:删除本条明细,没人再引用的异常头自动关闭。
await client.query(`DELETE FROM cost_exception_items WHERE order_item_id = $1`, [orderItemId])
await client.query(
`
UPDATE cost_exceptions ce
SET status = 'resolved', resolved_at = $1, item_count = 0, last_seen_at = $1
WHERE ce.status <> 'resolved'
AND NOT EXISTS (
SELECT 1 FROM cost_exception_items i WHERE i.exception_id = ce.id
)
`,
[now],
)
return {
orderItemId,
orderId: Number(row.order_id_resolved),
@@ -582,7 +615,7 @@ async function matchManualFulfillmentConfigWithClient(
)
)
WHERE oi.id = $1
AND oi.fulfillment_mode = 'manual'
AND oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
AND oi.fulfillment_config_id IS NULL
ORDER BY
(cfg.canonical_product_id IS NOT NULL
@@ -820,7 +853,7 @@ export async function getManualFulfillmentCountsByOrderIds(
LEFT JOIN manual_fulfillment_configs cfg ON cfg.id = oi.fulfillment_config_id
LEFT JOIN fulfilled ON fulfilled.order_item_id = oi.id
WHERE oi.order_id = ANY($1::bigint[])
AND oi.fulfillment_mode = 'manual'
AND oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
GROUP BY oi.order_id
`,
[ids],