diff --git a/apps/backend/src/services/finance/sales-facts-channel-service.ts b/apps/backend/src/services/finance/sales-facts-channel-service.ts index 3b66d640..b0c46386 100644 --- a/apps/backend/src/services/finance/sales-facts-channel-service.ts +++ b/apps/backend/src/services/finance/sales-facts-channel-service.ts @@ -73,7 +73,13 @@ export async function rebuildChannelSalesDailyFacts(input: { oi.id AS order_item_id, o.id AS order_id, COALESCE(oi.normalized_quantity, oi.quantity, 1)::numeric AS quantity, - COALESCE(oi.revenue_amount_fen, 0)::bigint AS revenue_fen + COALESCE(oi.revenue_amount_fen, 0)::bigint AS revenue_fen, + CASE + WHEN oi.fulfillment_channel = 'affiliate_dash' THEN 'sys_affiliate_purchase' + WHEN oi.fulfillment_channel = 'third_party_voucher' THEN 'sys_third_party_voucher_cost' + WHEN oi.fulfillment_channel = 'work_order' THEN 'sys_work_order_reward' + ELSE NULL + END AS channel_cost_code FROM order_items oi JOIN orders o ON o.id = oi.order_id WHERE oi.fulfillment_status IN ('assigned', 'pending') @@ -92,7 +98,27 @@ export async function rebuildChannelSalesDailyFacts(input: { s.*, COALESCE(rf.refund_fen, 0)::bigint AS refund_fen, COALESCE(c.cost_fen, 0)::bigint AS cost_fen, - (c.order_item_id IS NULL) AS cost_missing, + -- 成本缺失按「该渠道应有的成本项」判定,避免平台费快照把缺口掩盖掉。 + CASE + WHEN s.channel_cost_code IS NOT NULL THEN NOT EXISTS ( + SELECT 1 + FROM cost_snapshots cs + JOIN cost_items ci ON ci.id = cs.cost_item_id + WHERE cs.order_item_id = s.order_item_id + AND cs.superseded_at IS NULL + AND ci.code = s.channel_cost_code + ) + WHEN s.channel = 'manual' THEN NOT EXISTS ( + SELECT 1 FROM cost_snapshots cs + WHERE cs.order_item_id = s.order_item_id + AND cs.superseded_at IS NULL + AND cs.subject_type = 'manual_case' + ) + ELSE NOT EXISTS ( + SELECT 1 FROM cost_snapshots cs + WHERE cs.order_item_id = s.order_item_id AND cs.superseded_at IS NULL + ) + END AS cost_missing, COALESCE(c.has_actual, FALSE) AS has_actual FROM scope s LEFT JOIN refunded rf ON rf.order_item_id = s.order_item_id