fix(finance): 销售事实只统计已支付订单并扣减退款
- 事实范围增加 pay_status IN ('paid','refunded')
- 关联 order_refunds 已确认退款,净收入/毛利按扣除退款计算
- 补回归断言,防止再次出现退款不减收入
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user