This commit is contained in:
2026-08-12 12:47:06 +08:00
parent e4f65dae5f
commit d9bbd94366
4 changed files with 29 additions and 5 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ spring:
- id: firstleg-service
uri: lb://firstleg-service
predicates:
- Path=/api/merchant/v1/first-leg/**,/api/merchant/v1/customs/**,/api/platform/v1/first-leg/**,/api/platform/v1/customs/**,/api/platform/v1/port-schedules/**,/api/platform/v1/carrier-schedule-queries/**,/api/platform/v1/containers/**,/api/platform/v1/ocean-bookings/**,/api/platform/v1/truck-appointments/**,/api/platform/v1/fba-pricing/**,/api/platform/v1/inbounds/**,/api/platform/v1/fee-items/**,/api/platform/inbounds/**,/openapi/v1/inbound-measurements/**
- Path=/api/merchant/v1/first-leg/**,/api/merchant/v1/customs/**,/api/platform/v1/first-leg/**,/api/platform/v1/customs/**,/api/platform/v1/port-schedules/**,/api/platform/v1/carrier-schedule-queries/**,/api/platform/v1/containers/**,/api/platform/v1/ocean-bookings/**,/api/platform/v1/truck-appointments/**,/api/platform/v1/fba-pricing/**,/api/platform/v1/inbounds/**,/api/platform/v1/fee-items/**,/api/platform/v1/loading-plans/**,/api/platform/inbounds/**,/openapi/v1/inbound-measurements/**
- id: finance-service
uri: lb://finance-service
predicates:
@@ -328,6 +328,10 @@ export async function run(context) {
const path = `${basePath}?accountId=${encodeURIComponent(container.tenantId)}`
const body = { feeItemId: Number(feeItems.PAYABLE.id), direction: 'PAYABLE', businessDate: dateOffset(-2), amount: 300, currency: 'CNY', settlementIncluded: true, remark: `${context.marker}-CONTAINER_SHARED` }
const response = await request(context, 'POST', path, body)
if (response?.http === 404) {
blocker(context, '按柜费用来源接口已部署并可路由',
`POST ${path} returned HTTP 404 before Controller handling; Firstleg Controller contract is /api/platform/v1/loading-plans/{loadingPlanId}/fee-details, but current Gateway route does not expose /api/platform/v1/loading-plans/**; requestId=${response.requestId || 'missing'}, traceId=${response.traceId || 'missing'}`)
}
const detail = context.apiSuccess(MODULE, '创建按柜费用来源', response, '', OPTIONS)
const feeId = id(detail?.id)
if (feeId) { const approval = await request(context, 'POST', `${basePath}/${feeId}/approve?accountId=${encodeURIComponent(container.tenantId)}`); context.apiSuccess(MODULE, '审核按柜费用来源', approval, '', OPTIONS) }
@@ -22,9 +22,13 @@ function fakeContext(withFixture = true) {
]
const feeSources = [...receivables,
{ feeSourceId: '105', orderId: '205', direction: 'PAYABLE', currency: 'CNY',
businessDate: '2026-08-11', partnerId: '501', partnerName: 'Fixture Partner' }]
businessDate: '2026-08-11', partnerId: '501', partnerName: 'Fixture Partner' },
{ feeSourceId: null, sourceType: 'MANUAL', direction: 'PAYABLE', currency: 'CNY',
businessDate: '2026-08-10', billingBasis: 'CONTAINER_SHARED', partnerId: '501',
containerBookingId: 'CB-FAILED' }]
const fixture = { marker, customerId: '11',
orders: feeSources.map((item) => ({ orderId: item.orderId, shipmentId: `SHIP-${item.orderId}` })),
orders: feeSources.filter((item) => item.orderId)
.map((item) => ({ orderId: item.orderId, shipmentId: `SHIP-${item.orderId}` })),
feeSources: feeSources.map((item) => ({ ...item, marker })) }
const statements = new Map()
const settlements = new Map()
@@ -152,6 +156,9 @@ test('run 发布一条日期链、三张独立已出账单、三张 AR 和一张
assert.equal(fixture.payableSettlements[0].supplierId, '501')
assert.equal(fixture.payableSettlements[0].partnerId, '501')
assert.equal(fixture.payableSettlements[0].supplierName, 'Fixture Partner')
assert.equal(context.coverageGaps.some((gap) => gap.category === 'uncreated-fee-source-attempt'
&& gap.reason.includes('containerBookingId=CB-FAILED')), true)
assert.equal(context.calls.some((call) => call.body?.feeSourceIds?.includes(null)), false)
assert.equal(context.calls.some((call) => call.path.includes('/fee-pool')
|| call.body?.generationMode === 'BY_CUTOFF'), false)
assert.equal(context.steps.filter((step) => !step.passed).length, 0)
@@ -54,7 +54,7 @@ function normalizeFixture(context) {
|| !Array.isArray(root.orders) || !Array.isArray(root.feeSources)) return null
const customerId = id(root.customerId)
const orders = new Map(root.orders.map((order) => [id(order.orderId ?? order.id), order]))
const sources = root.feeSources.map((raw) => {
const attemptedSources = root.feeSources.map((raw) => {
const order = orders.get(id(raw.orderId))
return {
raw, feeSourceId: sourceId(raw), customerId: id(raw.customerId ?? customerId),
@@ -65,7 +65,11 @@ function normalizeFixture(context) {
supplierName: raw.supplierName ?? raw.partnerName ?? root.supplierName ?? root.partnerName ?? null,
}
})
return { root, customerId, sources }
return {
root, customerId,
sources: attemptedSources.filter((source) => source.feeSourceId),
uncreatedSources: attemptedSources.filter((source) => !source.feeSourceId),
}
}
async function createStatement(context, sources, action, name, generationMode = 'SELECTED') {
@@ -178,6 +182,15 @@ export async function run(context) {
finish(context, start)
return
}
if (fixture.uncreatedSources.length > 0) {
const evidence = fixture.uncreatedSources.map((source) => {
const raw = source.raw
return `sourceType=${raw.sourceType ?? 'missing'}, billingBasis=${raw.billingBasis ?? 'missing'}, containerBookingId=${id(raw.containerBookingId) || 'missing'}`
}).join('; ')
context.addCoverageGap(MODULE, SCENARIO, '忽略未创建的费用来源尝试',
`Finance 未返回 feeSourceId,不得作为费用来源消费:${evidence}`,
'uncreated-fee-source-attempt', { marker: context.marker, count: fixture.uncreatedSources.length })
}
const invalid = fixture.sources.filter((source) => !source.feeSourceId || !source.customerId
|| !source.businessDate || !source.waybillId || source.marker !== context.marker)
if (invalid.length > 0) {