import test from 'node:test' import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { assertReceivingWarehouse, normalizeReceivingCandidate, normalizeReceivingSession, normalizeReceivingWarehouseProfile } from '../src/api/receiving-adapter.js' import { receiving, receivingPhase, receivingProgress, resetReceivingSession, setCurrentReceiving } from '../src/stores/receiving.js' const read = (path) => readFile(new URL(path, import.meta.url), 'utf8') const inbounds = Array.from({ length: 15 }, (_, index) => ({ inboundCode: `NYEXX260827${String(index + 1).padStart(4, '0')}`, destinationType: index % 2 === 0 ? 'FBA' : 'OVERSEAS', fbaWarehouseCode: index % 2 === 0 ? 'ABE3' : null, destinationWarehouseCode: index % 2 === 0 ? 'ABE3' : 'US-LAX-01', plannedPalletCount: 1, totalPackages: 4, receivedPackages: index === 0 ? 4 : 0, pendingPackages: index === 0 ? 0 : 4 })) test('one container candidate aggregates fifteen independent one-inbound plans', () => { const candidate = normalizeReceivingCandidate({ sourceContainerBookingId: '2093166483653849089', containerNo: 'C10000000', status: 'WAIT_SIGN', overseasWarehouseCode: 'US-LAX-RCV', overseasWarehouseName: '洛杉矶接柜仓', plannedArrivalAt: '2026-08-29T09:30:00', totalOrderCount: 15, planCount: 15, inboundCodeCount: 15, missingInboundCodeCount: 0, plannedInboundCodeCount: 15, pendingPlanCount: 0, allPlansReady: true, operable: true, blockReason: '', destinationCodes: ['ABE3', 'US-LAX-01'], plannedPalletCount: 15, totalPackages: 60, receivedPackages: 0, currentAccount: false }) assert.deepEqual(candidate, { sourceContainerBookingId: '2093166483653849089', containerNo: 'C10000000', status: 'WAIT_SIGN', overseasWarehouseCode: 'US-LAX-RCV', overseasWarehouseName: '洛杉矶接柜仓', plannedArrivalAt: '2026-08-29T09:30:00', totalOrderCount: 15, planCount: 15, inboundCodeCount: 15, missingInboundCodeCount: 0, plannedInboundCodeCount: 15, pendingPlanCount: 0, allPlansReady: true, operable: true, blockReason: '', destinationCodes: ['ABE3', 'US-LAX-01'], plannedPalletCount: 15, totalPackages: 60, receivedPackages: 0, currentAccount: false }) assert.equal(candidate.planCount, candidate.inboundCodeCount) assert.equal(candidate.sourceContainerBookingId, '2093166483653849089') }) test('receiving warehouse facts are explicit and fail closed when absent or assigned elsewhere', () => { const profile = normalizeReceivingWarehouseProfile({ warehouseId: '900', warehouseCode: 'US-LAX-RCV', warehouseName: '洛杉矶接柜仓' }) const assigned = normalizeReceivingCandidate({ containerNo: 'C10000002', overseasWarehouseCode: 'US-LAX-RCV', overseasWarehouseName: '洛杉矶接柜仓' }) assert.equal(assertReceivingWarehouse(assigned, profile, '柜号 C10000002'), assigned) assert.throws(() => assertReceivingWarehouse(normalizeReceivingCandidate({ containerNo: 'C10000003' }), profile), (error) => { assert.equal(error.code, 'RECEIVING_WAREHOUSE_CONTRACT_INVALID') return /缺少已分配海外接柜仓/.test(error.message) }) assert.throws(() => assertReceivingWarehouse(normalizeReceivingCandidate({ containerNo: 'C10000004', overseasWarehouseCode: 'US-NYC-RCV', overseasWarehouseName: '纽约接柜仓' }), profile), (error) => { assert.equal(error.code, 'RECEIVING_WAREHOUSE_MISMATCH') return /已分配至其他海外接柜仓/.test(error.message) }) assert.throws(() => normalizeReceivingWarehouseProfile({ warehouseId: '900', warehouseName: '洛杉矶接柜仓' }), /绑定仓信息不完整/) }) test('incomplete containers remain visible but fail closed from server readiness facts', () => { const candidate = normalizeReceivingCandidate({ sourceContainerBookingId: '2093166483653849099', containerNo: 'C10000001', plannedArrivalAt: null, totalOrderCount: 15, inboundCodeCount: 12, planCount: 10, missingInboundCodeCount: 3, plannedInboundCodeCount: 10, pendingPlanCount: 5, allPlansReady: false, operable: false, blockReason: '存在3票缺少入仓码' }) assert.equal(candidate.totalOrderCount, 15) assert.equal(candidate.planCount, 10) assert.equal(candidate.plannedInboundCodeCount, 10) assert.equal(candidate.pendingPlanCount, 5) assert.equal(candidate.missingInboundCodeCount, 3) assert.equal(candidate.allPlansReady, false) assert.equal(candidate.operable, false) assert.equal(candidate.blockReason, '存在3票缺少入仓码') const absentGate = normalizeReceivingCandidate({ containerNo: 'OLD-CONTRACT' }) assert.equal(absentGate.allPlansReady, false) assert.equal(absentGate.operable, false) }) test('current recovery retains all independent-plan inbound facts while progress remains container-level', () => { const session = normalizeReceivingSession({ containerNo: 'C10000000', overseasWarehouseCode: 'US-LAX-RCV', overseasWarehouseName: '洛杉矶接柜仓', status: 'RECEIVING', totalPackages: 60, receivedPackages: 4, pendingPackages: 56, photos: [], inbounds }) setCurrentReceiving(session) assert.equal(receivingPhase(), 'receive') assert.deepEqual(receivingProgress(), { total: 60, received: 4, pending: 56, percent: 7 }) assert.equal(receiving.current.inbounds.length, 15) assert.equal(receiving.current.inbounds[0].inboundCode, 'NYEXX2608270001') assert.equal(receiving.current.inbounds[14].inboundCode, 'NYEXX2608270015') resetReceivingSession() }) test('container UI locks and signs once across independent plans, then renders only the scan-matched inbound', async () => { const [operation, summary, inboundApi] = await Promise.all([ read('../src/pages/operation/operation.vue'), read('../src/components/ReceivingSessionSummary.vue'), read('../src/api/inbound.js') ]) assert.match(operation, /item\.totalOrderCount[\s\S]*item\.inboundCodeCount[\s\S]*item\.planCount[\s\S]*item\.pendingPlanCount/) assert.match(summary, /inboundCodeCount[\s\S]*plannedPalletCount[\s\S]*totalPackages/) assert.doesNotMatch(operation, /v-for="[^"]*inbound[^"]*"/) assert.match(operation, /v-if="activeInbound"[\s\S]*activeInbound\.inboundCode[\s\S]*activeInbound\.pendingPackages/) assert.match(operation, /resolveCurrentCarton\(cartonMark\)/) assert.doesNotMatch(operation, /cartonMark\.split|\.split\([^\n]*['"]#['"]/) assert.match(inboundApi, /\$\{RECEIVING_BASE\}\/\$\{encodeURIComponent\(containerNo\)\}\/prepare/) assert.match(inboundApi, /current\('\/sign'\)/) assert.match(operation, /prepareReceivingContainer\(this\.selected\.containerNo, \{ idempotencyKey: key\.value \}\)/) assert.match(operation, /getCurrentReceiving\(\)[\s\S]*applyCurrentReceiving\(current\)/) assert.match(operation, /applyWarehouseProfile\(await getCurrentWarehouseProfile\(\)\)/) assert.match(operation, /values\.map\(\(item\) => this\.validateReceivingWarehouse/) assert.match(operation, /prepareReceivingContainer[\s\S]*validateReceivingWarehouse/) assert.match(summary, /overseasWarehouseCode[\s\S]*overseasWarehouseName/) for (const forbidden of ['customerId', 'planNo', 'allocationId', 'warehouseId', 'locationId']) { assert.doesNotMatch(inboundApi, new RegExp(forbidden)) } }) test('PDA does not infer container eligibility from partial plan workflow states', async () => { const [operation, adapter, inbound] = await Promise.all([ read('../src/pages/operation/operation.vue'), read('../src/api/receiving-adapter.js'), read('../src/api/inbound.js') ]) for (const internalStatus of ['PENDING_ASSIGNMENT', 'INBOUND_PLANNED', 'PDA_PENDING', 'ASSIGNED', 'PICKING']) { assert.doesNotMatch(operation, new RegExp(internalStatus)) } assert.match(operation, /listReceivingCandidates\(\)/) assert.match(operation, /statusLabel\(status\)[\s\S]*WAIT_SIGN[\s\S]*SIGNED[\s\S]*RECEIVING[\s\S]*COMPLETED/) assert.match(adapter, /allPlansReady: value\.allPlansReady === true/) assert.match(adapter, /operable: value\.operable === true/) assert.match(operation, /if \(!candidate\.operable\) \{ receiving\.selected = candidate; this\.detailOpen = true; return \}/) assert.match(operation, /if \(!this\.selected\?\.containerNo \|\| !this\.selected\?\.allPlansReady \|\| !this\.selected\?\.operable/) assert.match(operation, /recoverCurrentAfterConflict\(error, true\)/) assert.match(operation, /preserveEvidence \? error : null[\s\S]*this\.candidateError = preserveEvidence \? error : null/) assert.doesNotMatch(inbound, /sort\(/) })