From 5a48992d78094511fa0964d3b4e2a344cdb962a8 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 13 Aug 2026 09:52:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ance-reconciliation-full-contract.test.mjs | 25 +++++++++++++--- .../finance-reconciliation-full.mjs | 29 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/scripts/admin-test-env-modules/finance-reconciliation-full-contract.test.mjs b/scripts/admin-test-env-modules/finance-reconciliation-full-contract.test.mjs index 88a4d2267..68876aa97 100644 --- a/scripts/admin-test-env-modules/finance-reconciliation-full-contract.test.mjs +++ b/scripts/admin-test-env-modules/finance-reconciliation-full-contract.test.mjs @@ -11,7 +11,8 @@ const financePermissions = [ 'platform:finance:reconciliation:create', 'platform:finance:reconciliation:review', 'platform:finance:reconciliation:issue', 'platform:finance:reconciliation:adjust', 'platform:finance:reconciliation:void', 'platform:finance:settlement-ar:create', - 'platform:finance:settlement-ap:create', + 'platform:finance:settlement-ar:view', 'platform:finance:settlement-ap:create', + 'platform:finance:settlement-ap:view', ] function ok(data) { @@ -116,18 +117,26 @@ function fakeContext(withFixture = true, permissions = financePermissions, denyF const statement = statements.get(String(body.reconciliationStatementId)) assert.equal(statement.status, 'ISSUED') const settlement = { settlementId: String(++settlementSequence), direction: 'RECEIVABLE', - reconciliationStatementId: body.reconciliationStatementId, status: 'OPEN', - paymentStatus: 'UNPAID', version: 0 } + customerId: '11', reconciliationStatementId: body.reconciliationStatementId, status: 'OPEN', + paymentStatus: 'UNPAID', totalAmount: 100, paidAmount: 0, outstandingAmount: 100, version: 0 } settlements.set(settlement.settlementId, settlement) if (idem) idempotency.set(idem, settlement) return ok(structuredClone(settlement)) } if (method === 'POST' && requestPath.endsWith('/payable-settlements')) { const settlement = { settlementId: String(++settlementSequence), direction: 'PAYABLE', - supplierId: String(body.supplierId), status: 'OPEN', paymentStatus: 'UNPAID', version: 0 } + customerId: String(body.customerId), supplierId: String(body.supplierId), status: 'OPEN', + paymentStatus: 'UNPAID', totalAmount: 100, paidAmount: 0, outstandingAmount: 100, version: 0 } + settlements.set(settlement.settlementId, settlement) if (idem) idempotency.set(idem, settlement) return ok(structuredClone(settlement)) } + if (method === 'GET' && requestPath.startsWith('/api/platform/finance/v1/receivable-settlements?')) { + return ok([...settlements.values()].filter((item) => item.direction === 'RECEIVABLE')) + } + if (method === 'GET' && requestPath.startsWith('/api/platform/finance/v1/payable-settlements?')) { + return ok([...settlements.values()].filter((item) => item.direction === 'PAYABLE')) + } throw new Error(`Unexpected request: ${method} ${requestPath}`) }, addStep(module, name, response, passed, evidence = '', options = {}) { @@ -160,6 +169,10 @@ test('静态契约覆盖日期、SELECTED、三 AR、AP、状态机和数组输 assert.match(source, /receivables\.length < 4/) assert.match(source, /const groups = \[cutoffSources/) assert.match(source, /\/payable-settlements/) + assert.match(source, /findSettlementInOfficialList/) + assert.match(source, /`正式\$\{label\}列表包含同一 settlementId`/) + assert.match(source, /findSettlementInOfficialList\(context, 'RECEIVABLE'/) + assert.match(source, /findSettlementInOfficialList\(context, 'PAYABLE'/) assert.match(source, /fixture\.root\.statements = statementRows/) assert.match(source, /fixture\.root\.receivableSettlements = arRows/) assert.doesNotMatch(source, /generationMode:\s*['"]BY_CUTOFF['"]/) @@ -193,6 +206,10 @@ test('run 发布一条日期链、三张独立已出账单、三张 AR 和一张 assert.equal(context.calls.some((call) => call.body?.feeSourceIds?.includes(null)), false) assert.equal(context.calls.some((call) => call.body?.feeSourceIds?.includes('106')), false) assert.equal(context.calls.some((call) => call.path.includes('/fee-pool')), true) + assert.equal(context.calls.filter((call) => call.method === 'GET' + && call.path.includes('/receivable-settlements?customerId=11')).length, 3) + assert.equal(context.calls.filter((call) => call.method === 'GET' + && call.path.includes('/payable-settlements?customerId=11')).length, 1) assert.equal(context.calls.some((call) => call.body?.generationMode === 'BY_CUTOFF'), false) const failedSteps = context.steps.filter((step) => !step.passed) assert.equal(failedSteps.length, 0, JSON.stringify(failedSteps, null, 2)) diff --git a/scripts/admin-test-env-modules/finance-reconciliation-full.mjs b/scripts/admin-test-env-modules/finance-reconciliation-full.mjs index 4c3d3e0d2..9a4e4ac68 100644 --- a/scripts/admin-test-env-modules/finance-reconciliation-full.mjs +++ b/scripts/admin-test-env-modules/finance-reconciliation-full.mjs @@ -9,7 +9,9 @@ const REQUIRED_PERMISSIONS = [ 'platform:finance:reconciliation:adjust', 'platform:finance:reconciliation:void', 'platform:finance:settlement-ar:create', + 'platform:finance:settlement-ar:view', 'platform:finance:settlement-ap:create', + 'platform:finance:settlement-ap:view', ] function id(value) { return value == null ? '' : String(value) } @@ -43,6 +45,19 @@ function sameIds(actual, expected) { const right = [...new Set(expected)].sort() return left.length === right.length && left.every((value, index) => value === right[index]) } + +async function findSettlementInOfficialList(context, direction, customerId, targetId, label) { + const segment = direction === 'RECEIVABLE' ? 'receivable-settlements' : 'payable-settlements' + const response = await context.request('GET', + `${BASE}/${segment}?customerId=${encodeURIComponent(customerId)}`) + const summaries = context.apiSuccess(MODULE, `从正式${label}列表查回结算单`, response, + `settlementId=${targetId}, customerId=${customerId}`, OPTIONS) + const found = rows(summaries).find((item) => settlementId(item) === targetId) + context.assert(MODULE, `正式${label}列表包含同一 settlementId`, response, + found?.direction === direction && id(found?.customerId) === id(customerId), + `expected=${targetId}, actual=${settlementId(found) || 'missing'}, direction=${found?.direction ?? 'missing'}`, OPTIONS) + return found ?? null +} function dates(sources) { const values = sources.map((source) => source.businessDate).sort() return { start: values[0], end: values.at(-1) } @@ -214,6 +229,12 @@ async function createReceivable(context, statement, index) { const replay = context.apiSuccess(MODULE, `第 ${index} 张应收结算单幂等重放`, replayResponse, '', OPTIONS) context.assert(MODULE, `第 ${index} 张 AR 幂等保持同一资源`, replayResponse, settlementId(replay) === settlementId(settlement), `settlementId=${settlementId(settlement)}`, OPTIONS) + const listed = await findSettlementInOfficialList(context, 'RECEIVABLE', settlement.customerId, + settlementId(settlement), `应收第 ${index} 张`) + context.assert(MODULE, `第 ${index} 张 AR 正式列表金额与创建结果一致`, null, + listed && Number(listed.totalAmount) === Number(settlement.totalAmount) + && Number(listed.outstandingAmount) === Number(settlement.outstandingAmount), + `createdTotal=${settlement.totalAmount ?? 'missing'}, listedTotal=${listed?.totalAmount ?? 'missing'}`, OPTIONS) return settlement } @@ -387,6 +408,14 @@ export async function run(context) { const response = await write(context, 'POST', `${BASE}/payable-settlements`, body, 'payable-settlement') payableSettlement = context.apiSuccess(MODULE, '按 Fixture PAYABLE 与 partnerId/supplierId 生成应付结算单', response, `sources=${payables.length}, supplierId=${supplierId}, supplierName=${supplierName ?? 'null'}`, OPTIONS) + if (payableSettlement) { + const listed = await findSettlementInOfficialList(context, 'PAYABLE', fixture.customerId, + settlementId(payableSettlement), '应付') + context.assert(MODULE, 'AP 正式列表供应商与金额和创建结果一致', null, + listed && id(listed.supplierId) === id(payableSettlement.supplierId) + && Number(listed.totalAmount) === Number(payableSettlement.totalAmount), + `createdSupplier=${id(payableSettlement.supplierId)}, listedSupplier=${id(listed?.supplierId) || 'missing'}`, OPTIONS) + } if (!payableSettlement) { blocker(context, 'PAYABLE 真实接口创建失败', `HTTP=${response.http}, code=${response.body?.code ?? 'missing'}, requestId=${response.requestId ?? 'null'}, traceId=${response.traceId ?? 'null'}`,