Files
client-web/src/modules/first-leg/utils/firstLegOrder.test.mjs
T

212 lines
9.0 KiB
JavaScript
Raw Normal View History

2026-07-30 17:59:42 +08:00
import assert from 'node:assert/strict'
import test from 'node:test'
2026-07-31 15:34:22 +08:00
import { createFirstLegPartySnapshot } from '../data/firstLegParties.ts'
2026-07-30 17:59:42 +08:00
import {
canClientCancelFirstLegOrder,
canClientEditFirstLegOrder,
createEmptyCargoItem,
FIRST_LEG_PROGRESS_STAGES,
getFirstLegProgressIndex,
2026-07-30 17:59:42 +08:00
getFirstLegStatusLabel,
2026-09-15 18:20:34 +08:00
getFirstLegStatusTone,
2026-07-30 17:59:42 +08:00
getFirstLegTransportModeLabel,
summarizeFirstLegCargo,
validateFirstLegOrderDraft,
} from './firstLegOrder.ts'
2026-07-31 15:34:22 +08:00
const shipperParty = {
id: 'CP001', code: 'SHIPPER-001', name: '测试发货企业', roles: ['SHIPPER'], contactName: '测试联系人',
phone: '13800000000', email: 'shipper@example.test', countryRegion: '中国', province: '浙江省', city: '宁波市',
postalCode: '315000', address: '测试发货地址', status: 'ACTIVE',
}
const consigneeParty = {
id: 'CP002', code: 'CONSIGNEE-001', name: 'TEST CONSIGNEE LLC', roles: ['CONSIGNEE'], contactName: 'Test Receiver',
phone: '+1 555 0100', email: 'receiver@example.test', countryRegion: '美国', province: 'California', city: 'Los Angeles',
postalCode: '90001', address: '100 Test Logistics Ave', status: 'ACTIVE',
}
2026-07-30 17:59:42 +08:00
function validDraft() {
return {
referenceNo: 'REF-001',
transportMode: 'FCL',
businessType: 'EXPORT',
origin: '宁波',
destination: '洛杉矶',
receivingWarehouseId: 'WH005',
expectedDepartureDate: '2026-08-08',
shipperPartyId: 'CP001',
2026-07-31 15:34:22 +08:00
shipperPartySnapshot: createFirstLegPartySnapshot(shipperParty, 'SHIPPER'),
shipper: shipperParty.name,
2026-07-30 17:59:42 +08:00
consigneePartyId: 'CP003',
2026-07-31 15:34:22 +08:00
consigneePartySnapshot: createFirstLegPartySnapshot(consigneeParty, 'CONSIGNEE'),
consignee: consigneeParty.name,
2026-07-30 17:59:42 +08:00
priority: 'NORMAL',
customerRemark: '',
tradeTerm: 'FOB',
destinationCountryCode: 'US',
2026-08-04 11:14:29 +08:00
destinationWarehouseId: 'WH-FBA-ONT8',
2026-07-30 17:59:42 +08:00
destinationWarehouseName: 'ONT8',
pickupMethod: 'SELF_EXPRESS',
channelCode: 'SEA-US-FBA',
channelName: '美国海运渠道',
2026-08-07 14:47:00 +08:00
pricingTaxMode: 'TAX_INCLUDED',
2026-07-30 17:59:42 +08:00
taxMode: 'BUY_DECLARATION',
customsInspection: false,
destinationType: 'FBA',
amazonWarehouseCode: 'ONT8',
shipmentId: 'FBA-001',
2026-09-11 15:38:28 +08:00
items: [{ ...createEmptyCargoItem('cargo-1'), sku: 'SKU-001', upc: '012345678905', cargoNameCn: '家居用品', unitsPerPackage: 100, pieces: 100, grossWeightKg: 1200, volumeCbm: 3.5, shipmentId: 'FBA-001', referenceId: 'REF-001' }],
2026-07-30 17:59:42 +08:00
}
}
test('uses the confirmed status and transport labels', () => {
2026-08-06 13:56:13 +08:00
assert.equal(getFirstLegStatusLabel('WAIT_MEASURE'), '待测量数据')
assert.equal(getFirstLegStatusLabel('WAIT_PALLETIZE'), '待组托')
2026-08-06 13:56:13 +08:00
assert.equal(getFirstLegStatusLabel('WAIT_SPLIT'), '待分柜')
assert.equal(getFirstLegStatusLabel('LOADING'), '装柜中')
assert.equal(getFirstLegStatusLabel('LOADED'), '装柜完成')
assert.equal(getFirstLegStatusLabel('DELIVERED'), '派送完成')
2026-07-30 17:59:42 +08:00
assert.equal(getFirstLegTransportModeLabel('FCL'), '海运整箱 FCL')
assert.deepEqual(FIRST_LEG_PROGRESS_STAGES.map((item) => item.code), [
'PENDING_REVIEW', 'WAIT_MEASURE', 'WAIT_PALLETIZE', 'WAIT_SPLIT', 'LOADING', 'LOADED', 'DELIVERED',
])
assert.equal(getFirstLegProgressIndex('WAIT_PALLETIZE'), 2)
2026-07-30 17:59:42 +08:00
})
test('only allows draft orders to be edited in the confirmed phase-one flow', () => {
assert.equal(canClientEditFirstLegOrder('DRAFT'), true)
assert.equal(canClientEditFirstLegOrder('PENDING_REVIEW'), false)
assert.equal(canClientCancelFirstLegOrder('DRAFT'), true)
assert.equal(canClientCancelFirstLegOrder('PENDING_REVIEW'), true)
2026-08-06 13:56:13 +08:00
assert.equal(canClientCancelFirstLegOrder('WAIT_MEASURE'), false)
assert.equal(canClientCancelFirstLegOrder('WAIT_PALLETIZE'), false)
2026-08-06 13:56:13 +08:00
assert.equal(canClientCancelFirstLegOrder('WAIT_SPLIT'), false)
2026-07-30 17:59:42 +08:00
})
test('validates the confirmed minimum submission fields', () => {
assert.deepEqual(validateFirstLegOrderDraft(validDraft()), [])
const invalid = validDraft()
invalid.receivingWarehouseId = ''
invalid.items[0].grossWeightKg = 0
const errors = validateFirstLegOrderDraft(invalid)
assert.ok(errors.includes('请选择平台收货仓'))
assert.ok(errors.includes('第 1 条货物毛重必须大于 0'))
})
2026-09-11 15:38:28 +08:00
test('rejects a total quantity that does not equal units per package multiplied by package count', () => {
const draft = validDraft()
draft.items[0].unitsPerPackage = 24
draft.items[0].packageCount = 5
draft.items[0].pieces = 119
assert.ok(validateFirstLegOrderDraft(draft).includes('第 1 条货物总数量应等于每箱数量 × 箱数(120)'))
draft.items[0].pieces = 120
assert.ok(!validateFirstLegOrderDraft(draft).some((message) => message.includes('总数量应等于')))
})
2026-08-04 12:01:05 +08:00
test('only requires sender for pickup and receiver for a private destination', () => {
const publicDraft = validDraft()
publicDraft.shipperPartySnapshot = null
publicDraft.consigneePartySnapshot = null
assert.deepEqual(validateFirstLegOrderDraft(publicDraft), [])
2026-07-30 17:59:42 +08:00
2026-08-04 12:01:05 +08:00
const pickupDraft = validDraft()
pickupDraft.pickupMethod = 'PICKUP'
pickupDraft.shipperPartySnapshot = null
assert.ok(validateFirstLegOrderDraft(pickupDraft).includes('请填写提货联系人'))
const privateDraft = validDraft()
privateDraft.destinationType = 'PRIVATE'
privateDraft.destinationWarehouseId = ''
privateDraft.amazonWarehouseCode = ''
privateDraft.shipmentId = ''
privateDraft.referenceNo = ''
privateDraft.consigneePartySnapshot = null
assert.ok(validateFirstLegOrderDraft(privateDraft).includes('请填写收件人姓名'))
2026-07-30 17:59:42 +08:00
})
2026-08-31 13:02:08 +08:00
test('requires FBA warehouse and cargo-level shipment, reference and SKU while keeping UPC optional', () => {
const draft = validDraft()
draft.referenceNo = ''
draft.amazonWarehouseCode = ''
draft.shipmentId = ''
draft.items[0].sku = ''
draft.items[0].upc = ''
2026-08-07 19:23:46 +08:00
draft.items[0].shipmentId = ''
draft.items[0].referenceId = ''
const errors = validateFirstLegOrderDraft(draft)
assert.ok(errors.includes('FBA 订单请填写 Amazon 仓库编码'))
assert.ok(errors.includes('第 1 条货物缺少客户 SKU'))
2026-08-31 13:02:08 +08:00
assert.ok(!errors.includes('第 1 条货物缺少 UPC'))
2026-08-25 22:08:30 +08:00
assert.ok(errors.includes('第 1 条货物缺少货件编号'))
assert.ok(errors.includes('第 1 条货物缺少跟踪编号'))
})
2026-08-04 11:14:29 +08:00
test('requires public destination ids while keeping private addresses detached from public warehouses', () => {
const publicDraft = validDraft()
publicDraft.destinationWarehouseId = ''
assert.ok(validateFirstLegOrderDraft(publicDraft).includes('请选择平台维护的目的仓'))
const privateDraft = validDraft()
privateDraft.destinationType = 'PRIVATE'
privateDraft.destinationWarehouseId = 'WH-FBA-ONT8'
privateDraft.amazonWarehouseCode = ''
privateDraft.shipmentId = ''
privateDraft.referenceNo = ''
assert.ok(validateFirstLegOrderDraft(privateDraft).includes('私人地址不能引用平台公共仓库'))
})
test('requires customs inspection when any cargo is a wooden product', () => {
const draft = validDraft()
draft.items[0].woodenProduct = true
draft.customsInspection = null
assert.ok(validateFirstLegOrderDraft(draft).includes('第 1 条货物为木制品,“是否商检”必须选择“是”'))
draft.customsInspection = false
assert.ok(validateFirstLegOrderDraft(draft).includes('第 1 条货物为木制品,“是否商检”必须选择“是”'))
draft.customsInspection = true
assert.ok(!validateFirstLegOrderDraft(draft).some((message) => message.includes('为木制品')))
})
2026-07-30 17:59:42 +08:00
test('summarizes cargo values from line items', () => {
const first = validDraft().items[0]
const second = { ...first, id: 'cargo-2', pieces: 40, packageCount: 4, grossWeightKg: 300, volumeCbm: 1.25 }
assert.deepEqual(summarizeFirstLegCargo([first, second]), {
pieces: 140,
packageCount: 5,
grossWeightKg: 1500,
volumeCbm: 4.75,
})
})
2026-09-11 15:38:28 +08:00
test('counts one physical carton series for SKU lines in the same mixed-carton group', () => {
const first = { ...validDraft().items[0], packingGroupNo: 'A', packageCount: 10, pieces: 20, unitsPerPackage: 2 }
const second = { ...first, id: 'cargo-2', sku: 'SKU-002', pieces: 30, unitsPerPackage: 3 }
2026-09-11 16:50:23 +08:00
assert.deepEqual(summarizeFirstLegCargo([first, second]), {
pieces: 50,
packageCount: 10,
grossWeightKg: first.grossWeightKg,
volumeCbm: first.volumeCbm,
})
2026-09-11 15:38:28 +08:00
const draft = { ...validDraft(), fulfillmentType: 'ONE_CLICK_DROPSHIP', items: [first, second] }
2026-09-11 16:50:23 +08:00
assert.ok(!validateFirstLegOrderDraft(draft).some((message) => message.includes('同箱 SKU')))
2026-09-11 15:38:28 +08:00
second.packageCount = 9
2026-09-11 16:50:23 +08:00
assert.ok(validateFirstLegOrderDraft(draft).includes('同箱 SKU 的箱数、包装类型、箱规、重量和体积必须一致'))
2026-09-11 15:38:28 +08:00
})
2026-09-15 18:20:34 +08:00
test('distinguishes first-leg draft, review, active handling and completion', () => {
assert.equal(getFirstLegStatusTone('DRAFT'), 'info')
assert.equal(getFirstLegStatusTone('PENDING_REVIEW'), 'warning')
for (const status of ['WAIT_MEASURE', 'WAIT_PALLETIZE', 'WAIT_SPLIT', 'LOADING']) {
assert.equal(getFirstLegStatusTone(status), 'primary')
}
assert.equal(getFirstLegStatusTone('LOADED'), 'success')
assert.equal(getFirstLegStatusTone('DELIVERED'), 'success')
assert.equal(getFirstLegStatusTone('UNRECOGNIZED'), 'info')
})