- P0/P1:影子总账追加调整、关账/退款/资金/积分幂等、成本版本追溯、对账订单项维度 - 迁移收敛:原 103–114 合并为 103_finance_foundation、104_finance_controls、105_work_order_consignment - 数据:L1 钱包 BIGINT/CHECK/奖励幂等;重复奖励审计口径修正与人工核对清单 - 报表:销售毛利缺失成本收入提示,旧收支页明确现金流口径 - 文档:同步迁移基线、合并评估、代码复核与上线说明
155 lines
8.3 KiB
TypeScript
155 lines
8.3 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
import test from 'node:test'
|
|
|
|
const read = (path: URL) => readFile(path, 'utf8')
|
|
|
|
test('销售事实重算必须冻结已关账期间', async () => {
|
|
const source = await read(new URL('./sales-facts-service.ts', import.meta.url))
|
|
assert.ok(source.includes('finance_periods'), '重算必须读取会计期间')
|
|
assert.ok(source.includes("p.status = 'closed'"), '必须识别已关账期间')
|
|
assert.ok(source.includes('skippedClosedPeriods'), '必须返回被跳过的关账期间')
|
|
})
|
|
|
|
test('人工确认/退款不得静默改已关账期间', async () => {
|
|
const [manual, refund] = await Promise.all([
|
|
read(new URL('./manual-fulfillment-service.ts', import.meta.url)),
|
|
read(new URL('./order-refund-service.ts', import.meta.url)),
|
|
])
|
|
assert.ok(manual.includes('manual_fulfillment_period_closed'))
|
|
assert.ok(refund.includes('order_refund_period_closed'))
|
|
})
|
|
|
|
test('总账同步必须追加调整凭证,不能删除重建', async () => {
|
|
const source = await read(new URL('./finance-ledger-service.ts', import.meta.url))
|
|
assert.ok(!source.includes('DELETE FROM finance_journal_entries'), '已过账凭证不得删除重建')
|
|
assert.ok(source.includes('sales_revenue_adjustment'), '收入变化必须走调整凭证')
|
|
assert.ok(source.includes('sales_cost_adjustment'), '成本变化必须走调整凭证')
|
|
assert.ok(source.includes('finance_sales_sync:'), '按日同步必须加 advisory lock')
|
|
})
|
|
|
|
test('关账必须具备门槛、期间锁和重复关账保护', async () => {
|
|
const source = await read(new URL('./finance-ledger-service.ts', import.meta.url))
|
|
assert.ok(source.includes('assertPeriodCloseable'), '关账必须校验门槛')
|
|
assert.ok(source.includes('finance_period_close_blocked'), '关账失败必须给出明确错误码')
|
|
assert.ok(source.includes('finance_period_already_closed'), '重复关账必须拒绝')
|
|
assert.ok(source.includes('finance_period:'), '凭证写入与关账必须共用期间锁')
|
|
assert.ok(
|
|
source.includes('manual_cost_missing') || source.includes('未确认成本'),
|
|
'必须拦人工缺成本',
|
|
)
|
|
})
|
|
|
|
test('平台退款必须先校验可退金额并落退款意图', async () => {
|
|
const source = await read(new URL('./order-refund-service.ts', import.meta.url))
|
|
const platformStart = source.indexOf('export async function approveAndRecordKuaishouRefund')
|
|
const platformEnd = source.indexOf('type PlatformRefundIntentRow', platformStart)
|
|
const body = source.slice(platformStart, platformEnd)
|
|
assert.ok(
|
|
body.indexOf('getRefundableFen') < body.indexOf('approveKuaishouIndustryRefund'),
|
|
'可退金额校验必须发生在平台调用之前',
|
|
)
|
|
assert.ok(body.includes('beginPlatformRefundIntent'), '必须先落退款意图')
|
|
assert.ok(source.includes('finance_refund_intents'), '必须有平台退款状态机')
|
|
assert.ok(source.includes('order_refund_external_ref_exists'), '外部退款单号必须幂等')
|
|
assert.ok(source.includes('nextPayStatus'), '全额退款撤销必须恢复订单支付状态')
|
|
assert.ok(source.includes('订单已退款,成本异常自动关闭'), '撤销必须重新打开成本异常')
|
|
})
|
|
|
|
test('备用金对账确认必须按当前余额重算差额', async () => {
|
|
const source = await read(new URL('../admin/admin-fund-service.ts', import.meta.url))
|
|
const start = source.indexOf('export async function confirmAdminFundReconciliation')
|
|
const end = source.indexOf('export async function getAdminFundSupervisorOptions', start)
|
|
const body = source.slice(start, end)
|
|
assert.ok(body.includes('actualBalanceFen - currentBalanceFen'), '必须用确认时点余额重算')
|
|
assert.ok(body.includes('system_balance_fen = $5'), '必须回写确认时点的系统余额')
|
|
assert.ok(body.includes('difference_fen = $6'), '必须回写确认时点的差额')
|
|
})
|
|
|
|
test('迁移 104 提供退款/支出幂等与凭证完整性约束', async () => {
|
|
const migration = await read(
|
|
new URL('../../db/migrations/104_finance_controls.sql', import.meta.url),
|
|
)
|
|
assert.ok(migration.includes('uq_order_refunds_source_external_ref'))
|
|
assert.ok(migration.includes('uq_manual_expenses_order_item'))
|
|
assert.ok(migration.includes('finance_refund_intents'))
|
|
assert.ok(migration.includes('trg_finance_journal_balanced'))
|
|
assert.ok(migration.includes('trg_finance_journal_immutable'))
|
|
assert.ok(migration.includes('trg_fund_entries_balance'))
|
|
assert.ok(migration.includes('trg_fund_accounts_balance'))
|
|
assert.ok(migration.includes("'finance.post'"))
|
|
assert.ok(migration.includes("'finance.close'"))
|
|
assert.ok(migration.includes("'finance.reconcile'"))
|
|
})
|
|
|
|
test('财务写路由使用拆分后的权限,人工履约包含 finance', async () => {
|
|
const [ledgerRoute, financeRoute, orderRoute] = await Promise.all([
|
|
read(new URL('../../routes/admin/finance-ledger.ts', import.meta.url)),
|
|
read(new URL('../../routes/admin/finance.ts', import.meta.url)),
|
|
read(new URL('../../routes/admin/orders.ts', import.meta.url)),
|
|
])
|
|
assert.ok(ledgerRoute.includes("requireAdminPermission('finance.post')"))
|
|
assert.ok(ledgerRoute.includes("requireAdminPermission('finance.close')"))
|
|
assert.ok(ledgerRoute.includes("requireAdminPermission('finance.reconcile')"))
|
|
assert.ok(
|
|
financeRoute.includes("requireAdminRoles(['admin', 'operator', 'support', 'finance'])"),
|
|
'文档承诺财务可代录,路由必须允许 finance',
|
|
)
|
|
assert.ok(orderRoute.includes("requireAdminPermission('finance.post')"))
|
|
})
|
|
|
|
test('P1:资金/积分写命令支持 requestId 幂等', async () => {
|
|
const [fund, credit, migration] = await Promise.all([
|
|
read(new URL('../admin/admin-fund-service.ts', import.meta.url)),
|
|
read(new URL('../cost-center/platform-credit-service.ts', import.meta.url)),
|
|
read(new URL('../../db/migrations/104_finance_controls.sql', import.meta.url)),
|
|
])
|
|
assert.ok(fund.includes('requestId') && fund.includes('fund_request_duplicate'))
|
|
assert.ok(credit.includes('platform_credit_request_duplicate'))
|
|
assert.ok(migration.includes('uq_fund_entries_account_request'))
|
|
assert.ok(migration.includes('uq_platform_credit_entries_account_request'))
|
|
})
|
|
|
|
test('P1:成本版本追溯改价退休旧版本而不是覆盖金额', async () => {
|
|
const [product, affiliate] = await Promise.all([
|
|
read(new URL('../cost-center/product-catalog-service.ts', import.meta.url)),
|
|
read(new URL('../cost-center/affiliate-product-service.ts', import.meta.url)),
|
|
])
|
|
assert.ok(product.includes('新增一个从 targetTime 起生效的版本'))
|
|
assert.ok(product.includes("status = 'retired'"))
|
|
assert.ok(affiliate.includes('新增追溯版本'))
|
|
assert.ok(affiliate.includes("status = 'retired'"))
|
|
})
|
|
|
|
test('P1:平台对账支持订单项/平台明细维度', async () => {
|
|
const [service, migration] = await Promise.all([
|
|
read(new URL('./finance-reconciliation-service.ts', import.meta.url)),
|
|
read(new URL('../../db/migrations/104_finance_controls.sql', import.meta.url)),
|
|
])
|
|
assert.ok(service.includes('orderItemId') && service.includes('platformItemId'))
|
|
assert.ok(service.includes('order_item_id'))
|
|
assert.ok(migration.includes('platform_item_id'))
|
|
assert.ok(migration.includes('order_item_id'))
|
|
})
|
|
|
|
test('L1:钱包金额 BIGINT、非负约束与奖励流水幂等', async () => {
|
|
const migration = await read(
|
|
new URL('../../db/migrations/104_finance_controls.sql', import.meta.url),
|
|
)
|
|
assert.ok(migration.includes('ALTER COLUMN total_amount TYPE BIGINT'))
|
|
assert.ok(migration.includes('ALTER COLUMN available_amount TYPE BIGINT'))
|
|
assert.ok(migration.includes('chk_worker_wallets_nonnegative'))
|
|
assert.ok(migration.includes('chk_worker_wallet_ledgers_nonnegative_after'))
|
|
assert.ok(migration.includes('worker_wallet_ledger_event_key'))
|
|
assert.ok(migration.includes('uq_worker_wallet_ledger_event_key'))
|
|
assert.ok(migration.includes('v_worker_wallet_duplicate_rewards'))
|
|
assert.ok(migration.includes("NOT (NEW.payload_json ? 'inventoryId')"))
|
|
assert.ok(migration.includes('GROUP BY worker_id, related_work_order_id'))
|
|
})
|
|
|
|
test('销售毛利必须同时暴露缺失成本收入,避免只看毛利绝对值', async () => {
|
|
const source = await read(new URL('./sales-facts-service.ts', import.meta.url))
|
|
assert.ok(source.includes('cost_missing_revenue_fen'))
|
|
assert.ok(source.includes('costMissingRevenueFen'))
|
|
})
|