fix(finance): 销售事实只统计已支付订单并扣减退款

- 事实范围增加 pay_status IN ('paid','refunded')
- 关联 order_refunds 已确认退款,净收入/毛利按扣除退款计算
- 补回归断言,防止再次出现退款不减收入
This commit is contained in:
yml2213
2026-10-31 20:17:23 +08:00
parent 2f11ed6bca
commit 8e3b8e0a32
2 changed files with 31 additions and 3 deletions
@@ -152,3 +152,22 @@ test('销售毛利必须同时暴露缺失成本收入,避免只看毛利绝
assert.ok(source.includes('cost_missing_revenue_fen'))
assert.ok(source.includes('costMissingRevenueFen'))
})
test('销售事实必须只统计已支付订单,并扣减已确认退款', async () => {
const source = await read(new URL('./sales-facts-channel-service.ts', import.meta.url))
assert.ok(source.includes("o.pay_status IN ('paid', 'refunded')"), '必须过滤未支付订单')
assert.ok(source.includes('order_refunds'), '必须读取退款表')
assert.ok(source.includes("r.status = 'confirmed'"), '只计入已确认退款')
assert.ok(source.includes('SUM(revenue_fen) - SUM(refund_fen)'), '净收入必须扣减退款')
assert.ok(
source.includes('SUM(revenue_fen) - SUM(refund_fen) - SUM(cost_fen)'),
'毛利必须基于净收入',
)
})
test('人工确认后必须立刻重算该业务日事实与总账', async () => {
const source = await read(new URL('./manual-fulfillment-service.ts', import.meta.url))
assert.ok(source.includes('refreshFactsForOrderItem'), '确认后必须刷新事实')
assert.ok(source.includes('rebuildChannelSalesDailyFacts'), '必须重算渠道销售事实')
assert.ok(source.includes('syncFinanceLedgerFromFacts'), '必须同步总账')
})
@@ -77,16 +77,25 @@ export async function rebuildChannelSalesDailyFacts(input: {
FROM order_items oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_status IN ('assigned', 'pending')
AND o.pay_status IN ('paid', 'refunded')
AND (COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date
BETWEEN $1::date AND $2::date
),
refunded AS (
SELECT r.order_item_id, SUM(r.amount_fen)::bigint AS refund_fen
FROM order_refunds r
WHERE r.status = 'confirmed'
GROUP BY r.order_item_id
),
costed AS (
SELECT
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,
COALESCE(c.has_actual, FALSE) AS has_actual
FROM scope s
LEFT JOIN refunded rf ON rf.order_item_id = s.order_item_id
LEFT JOIN (
SELECT
cs.order_item_id,
@@ -114,8 +123,8 @@ export async function rebuildChannelSalesDailyFacts(input: {
COUNT(*)::int,
SUM(quantity),
SUM(revenue_fen),
0,
SUM(revenue_fen),
SUM(refund_fen),
SUM(revenue_fen) - SUM(refund_fen),
SUM(cost_fen),
COUNT(*) FILTER (WHERE cost_missing)::int,
CASE
@@ -124,7 +133,7 @@ export async function rebuildChannelSalesDailyFacts(input: {
WHEN BOOL_AND(has_actual) THEN 'actual'
ELSE 'estimated'
END,
SUM(revenue_fen) - SUM(cost_fen),
SUM(revenue_fen) - SUM(refund_fen) - SUM(cost_fen),
$3::timestamptz,
$3::timestamptz,
$3::timestamptz