99 lines
5.6 KiB
JavaScript
99 lines
5.6 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { normalizeModuleOrder } from '../admin-test-env-acceptance.mjs'
|
|
import { mixedReceivingSchedule, receiveMixedContainer } from './mixed-container-receiving.mjs'
|
|
|
|
function fixtures() {
|
|
const record = (type, fulfillmentType = 'STANDARD') => ({ label: type,
|
|
order: { destinationType: type, fulfillmentType }, source: { inboundId: type, inboundCode: type },
|
|
expectedCartonMarks: [1, 2, 3, 4].map(i => `${type}#4-${i}`),
|
|
overseas: { plan: { pallets: [{ palletNo: `PAL-${type}` }] } } })
|
|
const d = record('DROPSHIP', 'ONE_CLICK_DROPSHIP')
|
|
d.task = { pallets: [{ palletNo: 'DP-1', cartons: [{ inboundId: 'DROPSHIP', boxCode: 'DROPSHIP#4-1' }] },
|
|
{ palletNo: 'DP-2', cartons: [2, 3, 4].map(i => ({ inboundId: 'DROPSHIP', boxCode: `DROPSHIP#4-${i}` })) }] }
|
|
return [d, record('FBA'), record('OVERSEAS')]
|
|
}
|
|
|
|
test('mixed receiving is registered with prerequisites, not transfer or downstream delivery', () => {
|
|
assert.deepEqual(normalizeModuleOrder(['mixed-container-receiving']), ['merchant-qualification-fixture', 'mixed-container-receiving'])
|
|
const source = readFileSync(new URL('dropship-pallet-matrix.mjs', import.meta.url), 'utf8')
|
|
const start = source.indexOf("const dropship = await prepare('MIXED-DROPSHIP'")
|
|
const branch = source.slice(start, source.indexOf('if (stopBeforeOverseasPda)', start))
|
|
for (const entry of ["'MIXED-FBA', 1, {}, 'FBA'", "'MIXED-OVERSEAS', 1, {}, 'OVERSEAS'",
|
|
'allocate(group)', 'progressShipping(group)', 'convertOverseas(group)', 'receiveOverseas(group)',
|
|
'MIXED_CONTAINER_INBOUND_COMPLETED']) assert.ok(branch.includes(entry), entry)
|
|
assert.doesNotMatch(branch, /outboundOverseas|warehouse-transfers/)
|
|
assert.match(source, /混柜完成后按入仓码查询库存/)
|
|
assert.match(source, /String\(row.customerId\) === String\(item.order.tenantId\)/)
|
|
assert.match(source, /String\(p.warehousePalletId\) === String\(row.palletId\)/)
|
|
})
|
|
|
|
test('schedule interleaves three modes and uses FBA cumulative count rather than delta', () => {
|
|
const steps = mixedReceivingSchedule(fixtures())
|
|
assert.deepEqual(steps.map(s => s.path), ['bulk', 'pallet', 'carton', 'carton', 'carton', 'carton', 'bulk', 'pallet'])
|
|
assert.deepEqual(steps.filter(s => s.path === 'bulk').map(s => [s.count, s.body.packageCount]), [[1, 1], [3, 4]])
|
|
assert.equal(steps.reduce((sum, s) => sum + s.count, 0), 12)
|
|
assert.throws(() => mixedReceivingSchedule(fixtures().slice(0, 2)))
|
|
})
|
|
|
|
function driver({ earlyComplete = false, wrongModeAccepted = false, drift = false, repeatIncrement = false,
|
|
changedPayloadAccepted = false } = {}) {
|
|
const group = fixtures()
|
|
const expected = new Map(group.map(r => [r.source.inboundCode, 0]))
|
|
const schedule = mixedReceivingSchedule(group)
|
|
const seen = new Map()
|
|
let index = 0
|
|
let complete = false
|
|
const snapshot = () => ({ status: complete ? 'COMPLETED' : 'SIGNED', totalPackages: 12,
|
|
receivedPackages: [...expected.values()].reduce((a, b) => a + b, 0),
|
|
pendingPackages: 12 - [...expected.values()].reduce((a, b) => a + b, 0),
|
|
inbounds: [...expected].map(([inboundCode, receivedPackages]) => ({ inboundCode, totalPackages: 4,
|
|
receivedPackages, pendingPackages: 4 - receivedPackages })) })
|
|
return { group, marker: 'MIXED-TEST', check: (name, _response, passed) => assert.ok(passed, name),
|
|
request: async (_method, path, body) => {
|
|
if (body.idempotencyKey.endsWith('-duplicate')) {
|
|
return { body: path.endsWith('/carton') ? { code: 'A0409' }
|
|
: { code: '200', data: { result: 'ALREADY_RECEIVED', acceptedPackages: repeatIncrement ? 1 : 0 } } }
|
|
}
|
|
if (seen.has(body.idempotencyKey)) return { body: { code: changedPayloadAccepted ? '200' : 'A0409' } }
|
|
return { body: { code: (path.endsWith('/complete') ? earlyComplete : wrongModeAccepted) ? '200' : 'A0409' } }
|
|
},
|
|
call: async (_name, method, path, body) => {
|
|
if (method === 'GET') return snapshot()
|
|
if (seen.has(body.idempotencyKey)) return structuredClone(seen.get(body.idempotencyKey))
|
|
if (path.endsWith('/complete')) {
|
|
complete = true
|
|
const result = snapshot(); seen.set(body.idempotencyKey, result); return result
|
|
}
|
|
const step = schedule[index++]
|
|
assert.ok(path.endsWith(`/receipts/${step.path}`))
|
|
const inbound = drift ? 'FBA' : step.record.source.inboundCode
|
|
expected.set(inbound, expected.get(inbound) + step.count)
|
|
const result = { acceptedPackages: step.count }
|
|
seen.set(body.idempotencyKey, result)
|
|
return result
|
|
} }
|
|
}
|
|
|
|
test('mixed receiving executes the whole schedule and completes only after all three rows balance', async () => {
|
|
const result = await receiveMixedContainer(driver())
|
|
assert.equal(result.status, 'COMPLETED')
|
|
assert.equal(result.receivedPackages, 12)
|
|
})
|
|
test('mixed receiving fails on early-completion acceptance', async () => {
|
|
await assert.rejects(receiveMixedContainer(driver({ earlyComplete: true })))
|
|
})
|
|
test('mixed receiving fails if overseas or dropship can bypass pallet/carton receipt rules', async () => {
|
|
await assert.rejects(receiveMixedContainer(driver({ wrongModeAccepted: true })))
|
|
})
|
|
test('mixed receiving detects correct aggregate count allocated to the wrong inbound', async () => {
|
|
await assert.rejects(receiveMixedContainer(driver({ drift: true })))
|
|
})
|
|
test('mixed receiving rejects a new-key rescan that accepts another box', async () => {
|
|
await assert.rejects(receiveMixedContainer(driver({ repeatIncrement: true })))
|
|
})
|
|
test('mixed receiving rejects same-key different-payload success', async () => {
|
|
await assert.rejects(receiveMixedContainer(driver({ changedPayloadAccepted: true })))
|
|
})
|