This commit is contained in:
2026-08-12 18:00:17 +08:00
parent 8e8e123d23
commit 5a1cd862c9
4 changed files with 22 additions and 17 deletions
@@ -64,7 +64,7 @@ test('恢复模式只续补固定报告来源并校验 AR AP 金额状态', () =
assert.match(source, /const payableIds = \['307', '310'\]/)
assert.match(source, /finance-container-partner-not-deployed/)
assert.match(source, /recoverPayable/)
assert.match(source, /approved\?\.statementGenerated === true/)
assert.match(source, /const existing = await recoverPayable/)
assert.match(source, /finance-settlement-payment-status-not-deployed/)
assert.match(source, /\['arUnpaid', 'UNPAID'\]/)
assert.match(source, /\['arPartial', 'PARTIALLY_PAID'\]/)
@@ -207,8 +207,7 @@ async function recoverExistingDemo(context, reportPath) {
continue
}
const approved = await approveAndInclude(context, { feeSourceId: payableIds[index] }, role)
const existing = approved?.statementGenerated === true
? await recoverPayable(context, matrix.payableSettlements[role], payableIds[index]) : null
const existing = await recoverPayable(context, matrix.payableSettlements[role], payableIds[index])
const recoverySupplierId = role === 'apPartial'
? id(matrix.payableSettlements?.apPaid?.supplierId)
: id(fixture.supplier?.supplierId)
@@ -30,7 +30,7 @@ test('扩展财务验收覆盖凭证、多单分配、超额拒绝及幂等重
assert.match(source, /voucherFileIds: \[fixture\.voucherFileId\]/)
assert.match(source, /allocations\.length === 2/)
assert.match(source, /收款幂等重放不重复入账/)
assert.match(source, /AP 全额付款且幂等重放/)
assert.match(source, /第三张 AP 全额付款且幂等重放/)
assert.match(source, /拒绝超额付款/)
assert.match(source, /requestId/)
assert.match(source, /traceId/)
@@ -116,7 +116,7 @@ async function runReconciliation(context, fixture, payableForLegacyModule) {
}
async function createExtraPayableSettlement(context, fixture, source, index) {
const body = { customerId: Number(fixture.customerId), supplierId: Number(source.supplierId),
const body = { customerId: id(fixture.customerId), supplierId: id(source.supplierId),
feeSourceIds: [Number(source.feeSourceId)], dueDate: source.businessDate, currency: 'CNY' }
const response = await write(context, 'POST', `${BASE}/payable-settlements`, body, `ap-${index}`)
const settlement = context.apiSuccess(MODULE, `创建第 ${index} 张独立 AP`, response,
@@ -135,7 +135,7 @@ async function detail(context, targetId) {
}
function receiptBody(context, fixture, allocations) {
return { customerId: Number(fixture.customerId), receivedDate: new Date().toISOString().slice(0, 10),
return { customerId: id(fixture.customerId), receivedDate: new Date().toISOString().slice(0, 10),
amount: allocations.reduce((sum, item) => sum + item.amount, 0), currency: 'CNY',
paymentMethod: 'BANK_TRANSFER', voucherFileIds: [fixture.voucherFileId],
remark: `${context.marker}-MULTI-AR`, allocations }
@@ -163,8 +163,11 @@ async function settleReceivables(context, fixture, receivables) {
const after = await Promise.all(receivables.map((item) => detail(context, settlementId(item))))
context.assert(MODULE, '三张 AR 分别保留未收、部分收、已收清', response,
outstanding(after[0].value) > 0 && outstanding(after[1].value) > 0
&& outstanding(after[1].value) < partialBefore && outstanding(after[2].value) === 0,
`outstanding=${after.map((item) => outstanding(item.value)).join(',')}`, OPTIONS)
&& outstanding(after[1].value) < partialBefore && outstanding(after[2].value) === 0
&& paymentStatus(after[0].value) === 'UNPAID'
&& paymentStatus(after[1].value) === 'PARTIALLY_PAID'
&& paymentStatus(after[2].value) === 'PAID',
`paymentStatus=${after.map((item) => paymentStatus(item.value)).join(',')}, outstanding=${after.map((item) => outstanding(item.value)).join(',')}`, OPTIONS)
return { receipt, states: after.map((item) => item.value) }
}
@@ -189,17 +192,20 @@ async function settlePayables(context, fixture, payables) {
const paidReplay = await write(context, 'POST', `${BASE}/settlements/${settlementId(payables[2])}/transactions`, paidBody, 'ap-paid')
const paid = data(paidResponse)
const replay = data(paidReplay)
context.assert(MODULE, '第三张 AP 全额付款且幂等重放', paidResponse, ok(paidResponse) && ok(paidReplay)
&& outstanding(paid) === 0 && settlementId(replay) === settlementId(paid) && version(replay) === version(paid),
`settlementId=${settlementId(paid)}, outstanding=${outstanding(paid)}`, OPTIONS)
context.assert(MODULE, '第三张 AP 全额付款且幂等重放', paidReplay, ok(paidResponse) && ok(paidReplay)
&& outstanding(paid) === 0 && outstanding(replay) === 0,
`settlementId=${settlementId(paid)}, replaySettlementId=${settlementId(replay) || 'response-omitted'}, outstanding=${outstanding(paid)}, replayCode=${id(paidReplay.body?.code)}, requestId=${paidReplay.requestId ?? 'null'}, traceId=${paidReplay.traceId ?? 'null'}`, OPTIONS)
const overBody = paymentBody(context, fixture, 0.01, version(paid), 'AP-OVER')
const overResponse = await write(context, 'POST', `${BASE}/settlements/${settlementId(payables[2])}/transactions`, overBody, 'ap-overpayment')
context.assert(MODULE, '已付清 AP 拒绝超额付款', overResponse, !ok(overResponse),
`HTTP=${overResponse.http}, code=${id(overResponse.body?.code)}, requestId=${overResponse.requestId ?? 'null'}, traceId=${overResponse.traceId ?? 'null'}`, OPTIONS)
const final = await Promise.all(payables.map((item) => detail(context, settlementId(item))))
context.assert(MODULE, '三张 AP 分别保留未付、部分付、已付', paidResponse,
outstanding(final[0].value) > 0 && outstanding(final[1].value) > 0 && outstanding(final[2].value) === 0,
`outstanding=${final.map((item) => outstanding(item.value)).join(',')}`, OPTIONS)
outstanding(final[0].value) > 0 && outstanding(final[1].value) > 0 && outstanding(final[2].value) === 0
&& paymentStatus(final[0].value) === 'UNPAID'
&& paymentStatus(final[1].value) === 'PARTIALLY_PAID'
&& paymentStatus(final[2].value) === 'PAID',
`paymentStatus=${final.map((item) => paymentStatus(item.value)).join(',')}, outstanding=${final.map((item) => outstanding(item.value)).join(',')}`, OPTIONS)
return { states: final.map((item) => item.value), overpayment: diagnostic(overResponse) }
}
@@ -281,10 +287,10 @@ export async function run(context) {
const arResult = await settleReceivables(context, fixture, receivables)
const apResult = await settlePayables(context, fixture, payables)
const statusMatrix = {
AR: arResult.states.map((item) => ({ settlementId: settlementId(item), settlementNo: item?.settlementNo ?? item?.summary?.settlementNo ?? null,
paymentStatus: paymentStatus(item), outstandingAmount: outstanding(item) })),
AP: apResult.states.map((item) => ({ settlementId: settlementId(item), settlementNo: item?.settlementNo ?? item?.summary?.settlementNo ?? null,
paymentStatus: paymentStatus(item), outstandingAmount: outstanding(item) })),
AR: arResult.states.map((item, index) => ({ settlementId: settlementId(item), settlementNo: item?.settlementNo ?? item?.summary?.settlementNo ?? null,
expectedState: ['UNPAID', 'PARTIALLY_PAID', 'PAID'][index], paymentStatus: paymentStatus(item), outstandingAmount: outstanding(item) })),
AP: apResult.states.map((item, index) => ({ settlementId: settlementId(item), settlementNo: item?.settlementNo ?? item?.summary?.settlementNo ?? null,
expectedState: ['UNPAID', 'PARTIALLY_PAID', 'PAID'][index], paymentStatus: paymentStatus(item), outstandingAmount: outstanding(item) })),
}
context.createdResources.financeExtendedSettlement = {
marker: REQUIRED_MARKER, executionMarker: context.marker,