test(fulfillment): reconcile exact source positions and inventory balances
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import test from 'node:test'
|
||||
import { relabelOutboundMarksMatch, inventoryQuantitySnapshot, verifyPartialOrderCompletion,
|
||||
import { relabelOutboundMarksMatch, inventoryQuantitySnapshot, inventorySnapshot, verifyPartialOrderCompletion,
|
||||
completeReturnedException, verifyCustomerSignedDocuments, createSignedDocumentIsolationCustomer } from './phase5-overseas-fulfillment.mjs'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
@@ -169,12 +169,17 @@ test('same dispatch-return exception reaches actual restored inventory and retri
|
||||
let uploads = 0
|
||||
const source = { outboundPalletItemId: '51', inventoryPositionId: '61', maxReturnPackages: 1 }
|
||||
const context = contextFor((method, path, body) => {
|
||||
if (path.includes('/inventory/balances/1/positions')) return ok([{ id: '3', balanceId: '1',
|
||||
if (path.includes('/inventory/balances/1/positions')) return ok(path.includes('palletNo=') ? (completed ? [{ id: '3', balanceId: '1',
|
||||
customerId: '100', overseasWarehouseId: 'WH-1', inboundCode: 'IN-A', palletId: '71',
|
||||
palletNo: 'RET-TAIL-CURRENT', warehouseLocationId: '72', totalPackages: 1, availablePackages: 1 }])
|
||||
palletNo: 'RET-TAIL-CURRENT', warehouseLocationId: '72', totalPackages: 1, availablePackages: 1,
|
||||
holdPackages: 0, allocatedPackages: 0, pickedPackages: 0, receivingPackages: 0 }] : [])
|
||||
: [{ id: 'OLD', balanceId: '1', customerId: '100', overseasWarehouseId: 'WH-1', inboundCode: 'IN-A',
|
||||
palletNo: 'SOURCE', totalPackages: 8, availablePackages: 8, holdPackages: 0, allocatedPackages: 0, pickedPackages: 0, receivingPackages: 0 },
|
||||
...(completed ? [{ id: '3', balanceId: '1', customerId: '100', overseasWarehouseId: 'WH-1', inboundCode: 'IN-A', palletId: '71',
|
||||
palletNo: 'RET-TAIL-CURRENT', warehouseLocationId: '72', totalPackages: 1, availablePackages: 1, holdPackages: 0, allocatedPackages: 0, pickedPackages: 0, receivingPackages: 0 }] : [])])
|
||||
if (path.includes('/inventory/balances')) return ok({ total: 1, records: [{ id: '1', customerId: '100',
|
||||
overseasWarehouseId: 'WH-1', inboundCode: 'IN-A', totalPackages: completed ? 9 : 8,
|
||||
availablePackages: completed ? 9 : 8, holdPackages: 0, allocatedPackages: 0, pickedPackages: 0 }] })
|
||||
overseasWarehouseId: 'WH-1', totalPackages: completed ? 9 : 8,
|
||||
availablePackages: completed ? 9 : 8, holdPackages: 0, allocatedPackages: 0, pickedPackages: 0, receivingPackages: 0, inTransitPackages: 0, procurementPackages: 0 }] })
|
||||
if (path.includes('/inventory/ledger')) return ok({ total: completed ? 1 : 0, records: completed ? [{ id: '2',
|
||||
customerId: '100', inboundCode: 'IN-A', businessType: 'RETURN_STOCK_IN', businessOrderNo: 'EX-1',
|
||||
outboundOrderNo: 'OB-1', deltaPackages: 1, beforePackages: 0, afterPackages: 1 }] : [] })
|
||||
@@ -303,3 +308,77 @@ test('incomplete SIGNED-B producer remains a blocker and never logs in as the pr
|
||||
assert.ok(context.assertions.some(row => !row.passed))
|
||||
assert.equal(context.context.phase5TailVerification, undefined)
|
||||
})
|
||||
|
||||
function inventoryFixture() {
|
||||
const position = (positionId, inboundCode, packages) => ({ id: positionId, balanceId: '1', customerId: '100',
|
||||
overseasWarehouseId: 'WH-1', inboundCode, inboundOrderId: `ORDER-${inboundCode}`, palletId: `PALLET-${positionId}`,
|
||||
palletNo: `P-${positionId}`, batchNo: 'B-1', warehouseLocationId: 'LOCATION-1', warehouseLocationCode: 'L-1',
|
||||
totalPackages: packages, availablePackages: packages, holdPackages: 0, allocatedPackages: 0, pickedPackages: 0, receivingPackages: 0 })
|
||||
return { balance: { id: '1', customerId: '100', overseasWarehouseId: 'WH-1', skuCode: 'MIXED',
|
||||
totalPackages: 9, availablePackages: 9, holdPackages: 0, allocatedPackages: 0, pickedPackages: 0,
|
||||
receivingPackages: 0, inTransitPackages: 4, procurementPackages: 2 },
|
||||
positions: [position('11', 'IN-A', 3), position('12', 'IN-A-1', 2), position('13', 'IN-B', 4)] }
|
||||
}
|
||||
function inventoryContext(fixture) {
|
||||
return contextFor((method, path) => path.includes('/positions') ? ok(fixture.positions)
|
||||
: ok({ records: [fixture.balance], total: 1 }))
|
||||
}
|
||||
|
||||
test('inventory evidence resolves missing Balance inboundCode through exact positions and retains an independent shared balance', async () => {
|
||||
const fixture = inventoryFixture()
|
||||
const context = inventoryContext(fixture)
|
||||
const snapshot = await inventorySnapshot(context, '100', 'WH-1', ['IN-A', 'IN-A', 'IN-B'], 'stock')
|
||||
assert.equal(snapshot.length, 1)
|
||||
assert.equal(snapshot[0].id, '1')
|
||||
assert.equal(snapshot[0].totalPackages, 7)
|
||||
assert.deepEqual(snapshot[0].inboundCodes, ['IN-A', 'IN-B'])
|
||||
assert.deepEqual(snapshot[0].matchingPositionIds, ['11', '13'])
|
||||
assert.equal(snapshot[0].balance.totalPackages, 9)
|
||||
assert.equal(snapshot[0].balance.inTransitPackages, 4)
|
||||
assert.equal(snapshot[0].balance.procurementPackages, 2)
|
||||
assert.equal(snapshot[0].positions.length, 3)
|
||||
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
|
||||
assert.equal(context.calls.filter(call => call.path.includes('/positions')).length, 1)
|
||||
const childOnly = inventoryFixture()
|
||||
childOnly.positions = [childOnly.positions[1]]
|
||||
childOnly.balance.totalPackages = 2; childOnly.balance.availablePackages = 2
|
||||
assert.deepEqual(await inventorySnapshot(inventoryContext(childOnly), '100', 'WH-1', ['IN-A'], 'stock'), [])
|
||||
})
|
||||
|
||||
test('inventory evidence rejects foreign balances and positions, mismatched balance IDs, duplicate identities, and broken bucket reconciliation', async () => {
|
||||
for (const mutate of [
|
||||
fixture => { fixture.balance.customerId = 'OTHER' },
|
||||
fixture => { fixture.balance.overseasWarehouseId = 'OTHER' },
|
||||
fixture => { fixture.positions[0].customerId = 'OTHER' },
|
||||
fixture => { fixture.positions[0].overseasWarehouseId = 'OTHER' },
|
||||
fixture => { fixture.positions[0].balanceId = 'OTHER' },
|
||||
fixture => { fixture.positions[0].id = fixture.positions[1].id },
|
||||
fixture => { fixture.positions[0].inboundCode = '' },
|
||||
fixture => { fixture.positions[0].receivingPackages = 1 },
|
||||
fixture => { fixture.balance.inTransitPackages = null },
|
||||
fixture => { delete fixture.positions[0].receivingPackages },
|
||||
fixture => { fixture.balance.availablePackages = 8 },
|
||||
]) {
|
||||
const fixture = inventoryFixture(); mutate(fixture)
|
||||
const context = inventoryContext(fixture)
|
||||
assert.equal(await inventorySnapshot(context, '100', 'WH-1', ['IN-A'], 'stock'), null)
|
||||
assert.ok(context.assertions.some(row => !row.passed))
|
||||
}
|
||||
})
|
||||
|
||||
test('inventory evidence captures zero drift independently for every balance bucket, position identity, and unselected sibling position', async () => {
|
||||
const fixture = inventoryFixture()
|
||||
const original = await inventorySnapshot(inventoryContext(fixture), '100', 'WH-1', ['IN-A'], 'stock')
|
||||
for (const field of ['inTransitPackages', 'procurementPackages']) {
|
||||
const changed = inventoryFixture(); changed.balance[field] += 1
|
||||
assert.notDeepEqual(await inventorySnapshot(inventoryContext(changed), '100', 'WH-1', ['IN-A'], 'stock'), original)
|
||||
}
|
||||
for (const field of ['palletId', 'palletNo', 'batchNo', 'warehouseLocationId', 'warehouseLocationCode', 'inboundOrderId']) {
|
||||
const changed = inventoryFixture(); changed.positions[2][field] = 'DIFFERENT'
|
||||
assert.notDeepEqual(await inventorySnapshot(inventoryContext(changed), '100', 'WH-1', ['IN-A'], 'stock'), original)
|
||||
}
|
||||
for (const field of ['holdPackages', 'allocatedPackages', 'pickedPackages', 'receivingPackages']) {
|
||||
const changed = inventoryFixture(); changed.positions[2][field] += 1; changed.balance[field] += 1
|
||||
assert.notDeepEqual(await inventorySnapshot(inventoryContext(changed), '100', 'WH-1', ['IN-A'], 'stock'), original)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user