This commit is contained in:
2026-10-08 17:07:57 +08:00
parent 16d2d9111a
commit c1ddb658bd
111 changed files with 2914 additions and 941 deletions
@@ -2,16 +2,29 @@ import assert from 'node:assert/strict'
import test from 'node:test'
import { readFileSync } from 'node:fs'
import { fulfillmentHistorySnapshot, validTwoFragmentTree, validSignatureGate,
completionCounts, exactCompletionOutboxProof, resolveF40MerchantCredentials,
completionCounts, exactCompletionOutboxProof, rootCompletionTimeMatches, resolveF40MerchantCredentials,
f40TransferBody, f40LocalDateTime, validFragmentCabinetIdentity, validUnloadedRemainder,
validRemainderCancellationGate, exactCancelledRemainderOutboxProof, cancelUnloadedRemainder,
verifyRootProjections, verifyRemainderTaxIdentity, validFreshReceiptSave, preserveReceivingEvidence, run } from './phase5-root-fulfillment.mjs'
verifyRootProjections, verifyRemainderTaxIdentity, validFreshReceiptSave, preserveReceivingEvidence,
resolveF40ReceivingWarehouse, run } from './phase5-root-fulfillment.mjs'
import { loadOneFragment } from './phase3-order-splitting.mjs'
import { verifyPalletLabels } from './phase3-loading-finalize.mjs'
import { awaitProducerConsumption, convertReceivingContainers, runReceiving,
resolveSharedWarehouse } from './phase4-overseas-pda.mjs'
const source = readFileSync(new URL('./phase5-root-fulfillment.mjs', import.meta.url),'utf8')
test('根完成UTC时刻与上海本地签收最大值一致,拒绝时区偏移、较早来源和缺失证据', () => {
const times = ['2026-10-05T21:55:00.000', '2026-10-05T21:56:07.922']
assert.equal(rootCompletionTimeMatches('2026-10-05T13:56:07.922', times), true)
assert.equal(rootCompletionTimeMatches('2026-10-05T13:56:07.922Z', [...times].reverse()), true)
assert.equal(rootCompletionTimeMatches('2026-10-05T13:56:07.922Z', ['2026-10-05T21:56:07.922+08:00']), true)
for (const value of ['2026-10-05T21:56:07.922', '2026-10-05T13:55:00', null]) {
assert.equal(rootCompletionTimeMatches(value, times), false)
}
assert.equal(rootCompletionTimeMatches('2026-10-05T13:56:07.922', []), false)
assert.equal(rootCompletionTimeMatches('2026-10-05T13:56:07.922', ['invalid']), false)
})
const history = (...actions) => actions.map((action,index) => ({id:String(index+1),action}))
const tree = () => ({parentOrderId:'100',originalPackageCount:2,cumulativeLoadedPackageCount:2,
currentRemainderPackageCount:0,children:[
@@ -285,6 +298,85 @@ test('exported receiving warehouse resolver continues to choose only configured
]}}})
assert.deepEqual(await resolveSharedWarehouse(context),{id:'193000000000200003',name:'offline'})
})
function warehouseSelectionFixture() {
const producers = [1, 2].map(n => ({ tenantId: '90', containerNo: `CONTAINER-${n}`,
containerBookingId: `BOOKING-${n}`, sourceInboundId: `INBOUND-${n}`, inboundCode: `CODE-${n}` }))
const orders = producers.map((p, n) => ({ id: `LASTLEG-${n}`, customerId: p.tenantId,
sourceContainerBookingId: p.containerBookingId, sourceInboundId: p.sourceInboundId,
inboundCode: p.inboundCode, containerNo: p.containerNo, overseasWarehouseId: 'actual-warehouse',
overseasWarehouseCode: 'ACTUAL' }))
const warehouses = [{ id: 'other-warehouse', warehouseName: 'other', warehouseCode: 'OTHER',
warehouseType: 'OVERSEAS', status: 'ACTIVE' },
{ id: 'actual-warehouse', warehouseName: 'actual', warehouseCode: 'ACTUAL',
warehouseType: 'OVERSEAS', status: 'ACTIVE' }]
const calls = [], context = fixture()
context.request = async (method, path) => {
calls.push({ method, path })
const rows = path.includes('/warehouse-admin?') ? warehouses
: orders.filter(row => row.containerNo === new URL(path, 'https://offline.invalid').searchParams.get('containerNo'))
return { http: 200, body: { code: 200, data: { records: rows, total: rows.length } } }
}
return { producers, orders, warehouses, calls, context }
}
test('F40 selects actual source warehouse irrespective of authority list order and only performs GET', async () => {
for (const reverse of [false, true]) {
const f = warehouseSelectionFixture()
if (reverse) { f.warehouses.reverse(); f.orders.reverse(); f.producers.reverse() }
const warehouse = await resolveF40ReceivingWarehouse(f.context, f.producers)
assert.equal(warehouse.id, 'actual-warehouse')
assert.equal(warehouse.code, 'ACTUAL')
assert.equal(warehouse.sourceOrders.length, 2)
assert.ok(f.calls.every(row => row.method === 'GET'))
}
assert.ok(source.indexOf('await awaitProducerConsumption(scope,producers)')
< source.indexOf('await resolveF40ReceivingWarehouse(scope,producers)'))
assert.ok(source.indexOf('await resolveF40ReceivingWarehouse(scope,producers)')
< source.indexOf('await createPicker(scope, 7'))
assert.doesNotMatch(source, /resolveSharedWarehouse|19300000000020000[34]/)
})
test('F40 warehouse resolution rejects wrong customer, booking, inbound identity, duplicates and missing assignment', async () => {
for (const mutate of [
f => { f.orders[0].customerId = 'foreign' },
f => { f.orders[0].sourceContainerBookingId = 'foreign' },
f => { f.orders[0].sourceInboundId = 'foreign' },
f => { f.orders[0].inboundCode = 'foreign' },
f => { f.orders[0].containerNo = 'foreign' },
f => { f.orders[0].overseasWarehouseId = '' },
f => { f.orders.push({ ...f.orders[0] }) },
]) {
const f = warehouseSelectionFixture(); mutate(f)
await assert.rejects(resolveF40ReceivingWarehouse(f.context, f.producers), /来源柜客户入仓身份/)
assert.ok(f.calls.every(row => !row.path.includes('/warehouse-admin?')))
}
})
test('F40 warehouse resolution blocks different receiving warehouses and invalid or ambiguous official master data', async () => {
const mixed = warehouseSelectionFixture(); mixed.orders[1].overseasWarehouseId = 'other-warehouse'
await assert.rejects(resolveF40ReceivingWarehouse(mixed.context, mixed.producers), /同一实际接柜仓/)
for (const mutate of [
f => { f.warehouses.pop() },
f => { f.warehouses[1].status = 'DISABLED' },
f => { f.warehouses[1].warehouseType = 'DOMESTIC' },
f => { f.warehouses[1].warehouseCode = 'WRONG' },
f => { f.warehouses.push({ ...f.warehouses[1] }) },
]) {
const f = warehouseSelectionFixture(); mutate(f)
await assert.rejects(resolveF40ReceivingWarehouse(f.context, f.producers), /唯一启用海外收货仓/)
assert.ok(f.calls.every(row => row.method === 'GET'))
}
})
test('F40 warehouse resolution rejects empty or duplicate producers before HTTP', async () => {
for (const select of [() => [], f => [f.producers[0], f.producers[0]],
f => [{ ...f.producers[0], sourceInboundId: '' }]]) {
const f = warehouseSelectionFixture()
await assert.rejects(resolveF40ReceivingWarehouse(f.context, select(f)), /唯.*本轮实装来源/)
assert.equal(f.calls.length, 0)
}
})
test('complete orchestrator calls actual source chain and blocks missing Outbox evidence; no fake callback or SQL',() => {
assert.equal(typeof run,'function')
for(const name of ['orders','inbound-measurement','phase3-customs','phase3-outbound-dispatch',