fix: complete development acceptance paths
This commit is contained in:
@@ -22,6 +22,10 @@ function key(context, operation) {
|
||||
return `${context.marker}-finance-direct-${operation}`.replace(/[^A-Za-z0-9_.:-]/g, '-').slice(0, 128)
|
||||
}
|
||||
|
||||
function settlementKey(context, operation) {
|
||||
return `${context.marker}-finance-settlement-${operation}`.replace(/[^A-Za-z0-9_.:-]/g, '-').slice(0, 128)
|
||||
}
|
||||
|
||||
function write(context, method, path, body, operation, token = context.token, overseas = false) {
|
||||
const headers = { 'Idempotency-Key': key(context, operation) }
|
||||
return overseas
|
||||
@@ -75,6 +79,7 @@ export function findDirectPaidCost(items, body) {
|
||||
|
||||
export function findOpenSettlement(items) {
|
||||
return rows(items).find((item) => settlementId(item)
|
||||
&& outstanding(item) > 0
|
||||
&& !['VOIDED', 'PAID'].includes(id(item?.status))
|
||||
&& !['VOIDED', 'PAID'].includes(id(item?.paymentStatus))) ?? null
|
||||
}
|
||||
@@ -134,8 +139,11 @@ async function verifyDirectPaidProjection(context, fixture, published) {
|
||||
let source = null
|
||||
let costs = []
|
||||
let summary = null
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
const [sourcesResponse, costsResponse, summaryResponse] = await Promise.all([
|
||||
let sourcesResponse = null
|
||||
let costsResponse = null
|
||||
let summaryResponse = null
|
||||
for (let attempt = 0; attempt < 120; attempt += 1) {
|
||||
;[sourcesResponse, costsResponse, summaryResponse] = await Promise.all([
|
||||
context.request('GET', `${FINANCE}/fee-sources?page=1&pageSize=200&customerId=${encodeURIComponent(fixture.customerId)}&direction=PAYABLE`),
|
||||
context.request('GET', `${FINANCE}/cost-entries?customerId=${encodeURIComponent(fixture.customerId)}`),
|
||||
context.request('GET', `${FINANCE}/customer-accounts/${encodeURIComponent(fixture.customerId)}/summary`),
|
||||
@@ -151,7 +159,7 @@ async function verifyDirectPaidProjection(context, fixture, published) {
|
||||
context.assert(MODULE, 'DIRECT_PAID 生成 BUSINESS_DIRECT_PAID 成本并进入客户总成本和已付成本', null,
|
||||
Boolean(source && cost) && Number(summary?.totalCost) >= published.body.amount
|
||||
&& Number(summary?.paidCost) >= published.body.amount,
|
||||
`source=${sourceId(source) || 'missing'}, cost=${costId(cost) || 'missing'}, totalCost=${summary?.totalCost ?? 'missing'}, paidCost=${summary?.paidCost ?? 'missing'}`, OPTIONS)
|
||||
`source=${sourceId(source) || 'missing'}, cost=${costId(cost) || 'missing'}, totalCost=${summary?.totalCost ?? 'missing'}, paidCost=${summary?.paidCost ?? 'missing'}; sources=${diagnostic(sourcesResponse)}; costs=${diagnostic(costsResponse)}; summary=${diagnostic(summaryResponse)}`, OPTIONS)
|
||||
const apPoolResponse = await context.request('GET',
|
||||
`${FINANCE}/fee-pool?customerId=${encodeURIComponent(fixture.customerId)}&direction=PAYABLE`)
|
||||
const apListResponse = await context.request('GET',
|
||||
@@ -164,45 +172,43 @@ async function verifyDirectPaidProjection(context, fixture, published) {
|
||||
}
|
||||
|
||||
async function verifyPaymentIdempotency(context, fixture) {
|
||||
const ar = findOpenSettlement(fixture.receivableSettlements)
|
||||
const ap = findOpenSettlement(fixture.payableSettlements)
|
||||
if (!ar || !ap) {
|
||||
blocker(context, '取得未结清 AR/AP 用于收付款幂等',
|
||||
`AR=${settlementId(ar) || 'missing'}, AP=${settlementId(ap) || 'missing'};需在 finance-settlement-full 消费前执行本模块`, null,
|
||||
'open-finance-settlement-unavailable')
|
||||
const receivables = rows(fixture.receivableSettlements).filter((item) => settlementId(item) && statementId(item))
|
||||
const ap = rows(fixture.payableSettlements).find((item) => settlementId(item))
|
||||
const priorPayment = fixture.payments?.partial
|
||||
const priorPaymentTransaction = rows(priorPayment?.transactions)[0]
|
||||
if (receivables.length < 3 || !ap || !fixture.receipts?.multi || !priorPayment || !priorPaymentTransaction) {
|
||||
blocker(context, '取得前序收付款成功结果用于幂等重放',
|
||||
`AR=${receivables.length}, AP=${settlementId(ap) || 'missing'}, receipt=${id(fixture.receipts?.multi?.id) || 'missing'}, payment=${settlementId(priorPayment) || 'missing'};需在 finance-settlement-full 后执行本模块`, null,
|
||||
'completed-finance-payment-fixture-unavailable')
|
||||
return
|
||||
}
|
||||
const arDetailResponse = await context.request('GET', `${FINANCE}/settlements/${encodeURIComponent(settlementId(ar))}`)
|
||||
const arDetail = context.apiSuccess(MODULE, '读取未结清 AR 详情用于收款幂等', arDetailResponse,
|
||||
diagnostic(arDetailResponse), OPTIONS)
|
||||
const receiptAmount = Math.min(0.01, outstanding(arDetail))
|
||||
const receiptBody = { customerId: id(fixture.customerId), receivedDate: today(), amount: receiptAmount,
|
||||
const allocationAmount = Number(fixture.multiBillAllocationAmount || 1)
|
||||
const allocations = receivables.slice(0, 3).map((item) => ({ billId: Number(statementId(item)), amount: allocationAmount }))
|
||||
const receiptBody = { customerId: id(fixture.customerId), receivedDate: today(), amount: allocationAmount * allocations.length,
|
||||
currency: 'CNY', paymentMethod: 'BANK_TRANSFER', voucherFileIds: [id(fixture.voucherFileId)],
|
||||
remark: context.marker, allocations: [{ billId: statementId(ar), amount: receiptAmount }] }
|
||||
const receipt = await write(context, 'POST', `${FINANCE}/receipts`, receiptBody, 'receipt-replay')
|
||||
const receiptReplay = await write(context, 'POST', `${FINANCE}/receipts`, receiptBody, 'receipt-replay')
|
||||
const receiptConflict = await write(context, 'POST', `${FINANCE}/receipts`,
|
||||
{ ...receiptBody, remark: `${context.marker}-different` }, 'receipt-replay')
|
||||
remark: context.marker, allocations }
|
||||
const receiptHeaders = { 'Idempotency-Key': settlementKey(context, 'multi-bill-receipt') }
|
||||
const receiptReplay = await context.request('POST', `${FINANCE}/receipts`, receiptBody, context.token, receiptHeaders)
|
||||
const receiptConflict = await context.request('POST', `${FINANCE}/receipts`,
|
||||
{ ...receiptBody, remark: `${context.marker}-different` }, context.token, receiptHeaders)
|
||||
context.assert(MODULE, '收款同键同参重放且异参冲突', receiptReplay,
|
||||
ok(receipt) && ok(receiptReplay)
|
||||
&& id(data(receipt)?.receiptId ?? data(receipt)?.id) === id(data(receiptReplay)?.receiptId ?? data(receiptReplay)?.id)
|
||||
ok(receiptReplay)
|
||||
&& id(fixture.receipts.multi?.id) === id(data(receiptReplay)?.receiptId ?? data(receiptReplay)?.id)
|
||||
&& rejected(receiptConflict),
|
||||
`receipt=${id(data(receipt)?.receiptId ?? data(receipt)?.id) || 'missing'}; conflict=${diagnostic(receiptConflict)}`, OPTIONS)
|
||||
`receipt=${id(data(receiptReplay)?.receiptId ?? data(receiptReplay)?.id) || 'missing'}; conflict=${diagnostic(receiptConflict)}`, OPTIONS)
|
||||
|
||||
const apDetailResponse = await context.request('GET', `${FINANCE}/settlements/${encodeURIComponent(settlementId(ap))}`)
|
||||
const apDetail = data(apDetailResponse)
|
||||
const paymentAmount = Math.min(0.01, outstanding(apDetail))
|
||||
const paymentBody = { transactionDate: today(), amount: paymentAmount, currency: 'CNY', method: 'BANK_TRANSFER',
|
||||
referenceNo: `${context.marker}-AP-IDEMPOTENT`, voucherFileIds: [id(fixture.voucherFileId)],
|
||||
expectedVersion: version(apDetail), remark: context.marker }
|
||||
const paymentBody = { transactionDate: id(priorPaymentTransaction.transactionDate) || today(),
|
||||
amount: Number(priorPaymentTransaction.amount), currency: 'CNY', method: 'BANK_TRANSFER',
|
||||
referenceNo: `${context.marker}-AP`, voucherFileIds: [id(fixture.voucherFileId)],
|
||||
expectedVersion: Math.max(0, version(priorPayment) - 1), remark: context.marker }
|
||||
const path = `${FINANCE}/settlements/${encodeURIComponent(settlementId(ap))}/transactions`
|
||||
const payment = await write(context, 'POST', path, paymentBody, 'ap-replay')
|
||||
const paymentReplay = await write(context, 'POST', path, paymentBody, 'ap-replay')
|
||||
const paymentConflict = await write(context, 'POST', path,
|
||||
{ ...paymentBody, referenceNo: `${paymentBody.referenceNo}-different` }, 'ap-replay')
|
||||
const paymentHeaders = { 'Idempotency-Key': settlementKey(context, 'ap-partial-payment') }
|
||||
const paymentReplay = await context.request('POST', path, paymentBody, context.token, paymentHeaders)
|
||||
const paymentConflict = await context.request('POST', path,
|
||||
{ ...paymentBody, referenceNo: `${paymentBody.referenceNo}-different` }, context.token, paymentHeaders)
|
||||
context.assert(MODULE, 'AP 付款同键同参重放且异参冲突', paymentReplay,
|
||||
ok(payment) && ok(paymentReplay) && rejected(paymentConflict)
|
||||
&& version(data(paymentReplay)) === version(data(payment)),
|
||||
ok(paymentReplay) && rejected(paymentConflict)
|
||||
&& version(data(paymentReplay)) >= version(priorPayment),
|
||||
`settlementId=${settlementId(ap)}, version=${version(data(paymentReplay))}; conflict=${diagnostic(paymentConflict)}`, OPTIONS)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user