diff --git a/apps/backend/src/services/cost-center/cost-center-constants.ts b/apps/backend/src/services/cost-center/cost-center-constants.ts index 48082dbf..daca936f 100644 --- a/apps/backend/src/services/cost-center/cost-center-constants.ts +++ b/apps/backend/src/services/cost-center/cost-center-constants.ts @@ -67,7 +67,6 @@ export type FulfillmentChannel = | 'pending' | 'archived' | 'unclassified' - | 'refund_adjustment' export const FULFILLMENT_CHANNELS = new Set([ 'work_order', @@ -77,7 +76,6 @@ export const FULFILLMENT_CHANNELS = new Set([ 'pending', 'archived', 'unclassified', - 'refund_adjustment', ]) /** diff --git a/apps/backend/src/services/finance/order-refund-service.ts b/apps/backend/src/services/finance/order-refund-service.ts index 25faeb92..20bbceb0 100644 --- a/apps/backend/src/services/finance/order-refund-service.ts +++ b/apps/backend/src/services/finance/order-refund-service.ts @@ -329,11 +329,11 @@ export async function createManualRefund( }) // 比例类费用按净收入计:退款后重算相关订单项的费用快照,再回填事实。 + const refundedItems = await query<{ order_item_id: number }>( + 'SELECT DISTINCT order_item_id FROM order_refunds WHERE id = ANY($1::bigint[])', + [written.insertedIds], + ) try { - const refundedItems = await query<{ order_item_id: number }>( - 'SELECT DISTINCT order_item_id FROM order_refunds WHERE id = ANY($1::bigint[])', - [written.insertedIds], - ) for (const row of refundedItems.rows) { await syncChannelCostSnapshots({ afterId: Math.max(0, Number(row.order_item_id) - 1), @@ -345,8 +345,10 @@ export async function createManualRefund( // 快照重算失败不影响退款本身,成本快照管道会兜底。 } - const basisDate = toChinaDate(written.basisAt) - await refreshRefundDates(basisDate) + await refreshActiveRefundFacts( + refundedItems.rows.map((row) => Number(row.order_item_id)), + toChinaDate(written.basisAt), + ) const refunds = await listOrderRefunds(orderId) return { @@ -458,7 +460,14 @@ export async function voidOrderRefund( }) const rebuiltDate = toChinaDate(context.basisAt) - await refreshRefundDates(rebuiltDate) + const voidedItem = await query<{ order_item_id: number }>( + 'SELECT order_item_id FROM order_refunds WHERE id = $1', + [id], + ) + await refreshActiveRefundFacts( + voidedItem.rows.map((row) => Number(row.order_item_id)), + rebuiltDate, + ) const refunds = await listOrderRefunds(context.orderId) const refund = refunds.find((item) => item.id === id) if (!refund) { @@ -574,17 +583,20 @@ function toChinaDate(value: string): string { } /** - * 退款后回填事实与总账: - * - 原销售日:更新该订单项的收入/费用; - * - 退款日:生成历史订单(archived)的退款冲减行。 + * 退款后回填事实与总账:只处理断代后的订单项。 + * 历史归档订单的退款不进入新财务口径(业务侧自行处理)。 */ -async function refreshRefundDates(basisDate: string): Promise { - const refundDate = CHINA_DATE_FORMATTER.format(new Date()) - const dates = [basisDate, refundDate].filter(Boolean) - for (const date of [...new Set(dates)]) { - await rebuildChannelSalesDailyFacts({ dateFrom: date, dateTo: date }) - await syncFinanceLedgerFromFacts({ dateFrom: date, dateTo: date }) - } +async function refreshActiveRefundFacts(itemIds: number[], basisDate: string): Promise { + const ids = [...new Set(itemIds.filter((id) => Number.isInteger(id) && id > 0))] + if (ids.length === 0 || !basisDate) return + const active = await query<{ total: number }>( + `SELECT COUNT(*)::int AS total FROM order_items + WHERE id = ANY($1::bigint[]) AND fulfillment_status <> 'archived'`, + [ids], + ) + if (Number(active.rows[0]?.total || 0) === 0) return + await rebuildChannelSalesDailyFacts({ dateFrom: basisDate, dateTo: basisDate }) + await syncFinanceLedgerFromFacts({ dateFrom: basisDate, dateTo: basisDate }) } function formatFen(value: number): string { 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 b0c46386..2acfb934 100644 --- a/apps/backend/src/services/finance/sales-facts-channel-service.ts +++ b/apps/backend/src/services/finance/sales-facts-channel-service.ts @@ -169,47 +169,9 @@ export async function rebuildChannelSalesDailyFacts(input: { [dateFrom, dateTo, computedAt], ) - // 断代后历史订单的退款:按退款发生日记冲减行,否则这类退款会彻底丢失。 - const adjustments = await client.query( - ` - WITH adj AS ( - SELECT - (r.refunded_at AT TIME ZONE 'Asia/Shanghai')::date AS stat_date, - o.provider, - o.shop_id, - COUNT(DISTINCT o.id)::int AS order_count, - COUNT(*)::int AS item_count, - SUM(r.amount_fen)::bigint AS refund_fen - FROM order_refunds r - JOIN order_items oi ON oi.id = r.order_item_id - JOIN orders o ON o.id = oi.order_id - WHERE r.status = 'confirmed' - AND oi.fulfillment_status = 'archived' - AND (r.refunded_at AT TIME ZONE 'Asia/Shanghai')::date - BETWEEN $1::date AND $2::date - GROUP BY 1, 2, 3 - ) - INSERT INTO sales_daily_facts ( - stat_date, provider, shop_id, canonical_product_id, channel, - order_count, item_count, quantity, - gross_revenue_fen, refund_amount_fen, net_revenue_fen, - cost_amount_fen, cost_missing_count, cost_stage, - gross_profit_fen, computed_at, created_at, updated_at - ) - SELECT - stat_date, provider, shop_id, NULL, 'refund_adjustment', - order_count, item_count, 0, - 0, refund_fen, -refund_fen, - 0, 0, 'actual', - -refund_fen, $3::timestamptz, $3::timestamptz, $3::timestamptz - FROM adj - `, - [dateFrom, dateTo, computedAt], - ) - return { deleted: deleted.rowCount || 0, - inserted: (inserted.rowCount || 0) + (adjustments.rowCount || 0), + inserted: inserted.rowCount || 0, skipped, } }) diff --git a/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx b/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx index 3c0e9ed4..a51a702c 100644 --- a/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx +++ b/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx @@ -41,7 +41,6 @@ const FULFILLMENT_MODE_OPTIONS = [ { value: 'third_party_voucher', label: '第三方卡券' }, { value: 'manual', label: '人工客服' }, { value: 'unclassified', label: '未分类' }, - { value: 'refund_adjustment', label: '历史退款冲减' }, { value: 'archived', label: '历史归档' }, ] @@ -53,7 +52,6 @@ const FULFILLMENT_MODE_LABEL: Record = { manual: '人工客服', archived: '历史归档', unclassified: '未分类', - refund_adjustment: '历史退款冲减', } const COST_STAGE_LABEL: Record = { diff --git a/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx b/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx index 9b5b41c7..18c9fffb 100644 --- a/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx +++ b/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx @@ -52,7 +52,6 @@ const FULFILLMENT_MODE_LABELS: Record = { pending: '待分类', archived: '历史归档', unclassified: '未分类', - refund_adjustment: '历史退款冲减', } const REASON_LABELS: Record = { diff --git a/apps/frontend/src/types/admin/cost-center.ts b/apps/frontend/src/types/admin/cost-center.ts index 3ce63cd0..6363f8af 100644 --- a/apps/frontend/src/types/admin/cost-center.ts +++ b/apps/frontend/src/types/admin/cost-center.ts @@ -8,7 +8,6 @@ export type FulfillmentChannel = | 'third_party_voucher' | 'legacy_platform' | 'manual' - | 'refund_adjustment' /** 商品来源:由 canonical_sku 前缀推导,空串表示未分类。 */ export type ProductSource = 'affiliate_dash' | 'work_order' | 'third_party_voucher' | 'manual' | ''