Files
baiyue-firstleg/scripts/real-order-xlsx-test-env.test.mjs
T
2026-09-09 11:55:06 +08:00

114 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import test from 'node:test'
import { normalizeRows, channelName, matchRoute, measurementShape, buildSourceDraft, buildPayload,
requireSuccess, executeCandidates, totalPreservingShapes } from './real-order-xlsx-test-env.mjs'
const header = ['进仓编号', '客户', '渠道', '亚马逊仓', '实际件数', '实际毛重', '实际立方', '预报件数', '买单/退税', '品名', 'SKU', 'FBA货件编号', '跟踪编号']
const row = ['ABCD2606010001', 'Example', '合德海卡包税', 'ABC1', 31, 100, 2, 31, '买单', 'Goods', 'SKU1', 'SHIP1', 'REF1']
const order = () => normalizeRows([header, row]).orders[0]
const catalog = { receiving: { id: '1', warehouseName: 'Origin' },
channels: [{ channelName: '合德海卡', channelCode: 'CHANNEL', pricingTaxModes: ['TAX_INCLUDED'] }],
destinations: [{ id: '2', warehouseType: 'FBA', warehouseCode: 'ABC1', warehouseName: 'ABC1', countryCode: 'US' }] }
test('source counts remain actual, including after forecast differs', () => {
const modified = [...row]; modified[7] = 99
assert.equal(normalizeRows([header, modified]).orders[0].packageCount, 31)
})
test('missing and zero actual data do not fall back to forecast', () => {
for (const missing of [null, '', 0]) {
const modified = [...row]; modified[4] = missing
assert.ok(normalizeRows([header, modified]).orders[0].problems.includes('MISSING_实际件数'))
}
})
test('invalid source codes are excluded, not converted into orders', () => {
const modified = [...row]; modified[0] = 'container subtotal'
const actual = normalizeRows([header, modified])
assert.equal(actual.orders.length, 0); assert.equal(actual.excluded.length, 1)
})
test('explicit declaration changes use the stated final mode', () => {
for (const [source, target] of [['买单改退税', 'TAX_REFUND'], ['退税改买单', 'BUY_DECLARATION']]) {
const modified = [...row]; modified[8] = source
const result = normalizeRows([header, modified]).orders[0]
assert.equal(result.taxMode, target); assert.deepEqual(result.problems, [])
}
})
test('split rows aggregate actual counts once and reconcile forecast', () => {
const a = [...row], b = [...row]; a[4] = 15; b[4] = 16
const result = normalizeRows([header, a, b]).orders[0]
assert.equal(result.packageCount, 31); assert.deepEqual(result.rows, [2, 3]); assert.deepEqual(result.problems, [])
b[4] = 15
assert.ok(normalizeRows([header, a, b]).orders[0].problems.includes('SPLIT_COUNT_MISMATCH'))
})
test('conflicting channels on same inbound code block import', () => {
const b = [...row]; b[2] = '其他包税'
assert.ok(normalizeRows([header, row, b]).orders[0].problems.includes('CONFLICT_渠道'))
})
test('channel suffix normalization never substitutes another base channel', () => {
assert.equal(channelName('合德海派包税\n(FedEx)'), '合德海派')
assert.equal(channelName('合德海卡不包税'), '合德海卡')
assert.throws(() => matchRoute({ ...order(), channelName: 'Unknown' }, catalog))
})
test('excluded-tax estimation uses source billing unit and never guesses missing units', () => {
const modified = [...row]; modified[2] = '合德海卡不包税'
const columns = [...header, '单位']
const result = normalizeRows([columns, [...modified, 'CBM']]).orders[0]
assert.equal(result.chargeMode, 'CBM'); assert.deepEqual(result.problems, [])
assert.ok(normalizeRows([columns, [...modified, '']]).orders[0].problems.includes('UNKNOWN_OR_CONFLICTING_CHARGE_MODE'))
})
test('ambiguous destinations and unavailable tax modes fail closed', () => {
assert.throws(() => matchRoute(order(), { ...catalog, destinations: [...catalog.destinations, ...catalog.destinations] }))
assert.throws(() => matchRoute({ ...order(), pricingTaxMode: 'TAX_EXCLUDED' }, catalog))
})
test('private addresses and dropship are explicit gaps, not random replacements', () => {
assert.throws(() => matchRoute({ ...order(), destinationType: 'PRIVATE' }, catalog), /PRIVATE_ADDRESS/)
assert.throws(() => matchRoute({ ...order(), fulfillmentType: 'ONE_CLICK_DROPSHIP' }, catalog), /WORK_PALLET/)
})
test('logical pallet count uses ceiling of boxes divided by 30', () => {
for (const [boxes, expected] of [[1, 1], [30, 1], [31, 2], [300, 10], [1000, 34]]) {
assert.equal(measurementShape({ ...order(), packageCount: boxes }).palletCount, expected)
}
})
test('inferred groups conserve volume after ADD and server per-carton rounding', () => {
const rules = [[30, 20, 20], [40, 30, 30], [50, 40, 40], [60, 50, 50]].map((dims, index) =>
({ boxType: String(index), innerLengthCm: dims[0], innerWidthCm: dims[1], innerHeightCm: dims[2], edgeAdditionCm: 2 }))
for (const [packageCount, volumeCbm] of [[8, 0.403], [17, 1.545], [7, 0.626], [31, 2], [300, 23.457], [1, 0.1], [1000, 98.7654]]) {
const plan = totalPreservingShapes({ ...order(), packageCount, volumeCbm }, rules)
assert.ok(plan.length <= 2)
assert.equal(plan.reduce((n, g) => n + g.count, 0), packageCount)
const ticks = plan.reduce((n, g) => {
const raw = Number(((g.length + 2 * g.edgeAdditionCm) ** 3 / 1e6).toFixed(6))
return n + Math.ceil(raw * 10000 - 1e-8) * g.count
}, 0)
assert.equal(ticks, Math.round(volumeCbm * 10000))
if (Number.isInteger(Math.round(volumeCbm * 1e6) / 1000)) {
const detailWeight = plan.reduce((sum, group) => sum + Math.ceil(group.unitVolumeCbm * group.count * 167 * 1000 - 1e-8), 0)
assert.equal(detailWeight, Math.ceil(volumeCbm * 167 * 1000 - 1e-8))
}
}
assert.throws(() => totalPreservingShapes({ ...order(), volumeCbm: 0.123456 }, rules), /PRECISION/)
})
test('source-only draft does not invent measurement or product evidence', () => {
const payload = buildSourceDraft(order(), matchRoute(order(), catalog), 'Example', 'MARKER')
assert.equal(payload.cargoes[0].packageCount, 31)
assert.equal(payload.cargoes[0].grossWeightKg, 100)
assert.equal(payload.cargoes[0].volumeCbm, 2)
assert.equal(payload.cargoes[0].woodenProduct, false)
for (const key of ['lengthCm', 'productImageFileId', 'hsCode', 'dangerousGoods']) assert.equal(key in payload.cargoes[0], false)
})
test('simulated payload is labeled, preserving source measures without randomization', () => {
const payload = buildPayload(order(), matchRoute(order(), catalog), 'Example', 'image', 'MARKER')
assert.match(payload.customerRemark, /非真实测量证据/)
assert.equal(payload.cargoes[0].volumeCbm, 2)
assert.match(payload.cargoes[0].cargoRemark, /禁止用于真实报关/)
})
test('HTTP business errors preserve diagnostic evidence', () => {
assert.throws(() => requireSuccess({ http: 200, path: '/example', body: { code: 'A0409' }, requestId: 'req' }),
(error) => error.evidence.code === 'A0409' && error.evidence.requestId === 'req')
})
test('same batch never recreates an attempted order, even when interrupted', async () => {
const value = order(), state = { identity: { batchId: 'test' }, orders: { [value.key]: { status: 'STARTED' } } }
await executeCandidates({ candidates: [{ order: value, route: catalog }], limit: 1, state, save: async () => {},
api: async () => { throw Error('must not call HTTP') } })
assert.equal(state.lastRun.attempted, 0); assert.equal(state.lastRun.skipped, 1)
})