282 lines
20 KiB
JavaScript
282 lines
20 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
import { fulfillment, fulfillmentIdempotencyKey, resetFulfillmentSession, restoreFulfillmentDraft, updateFulfillmentDraft } from '../src/stores/fulfillment.js'
|
|
import {
|
|
reviewDispatchLoading, completeDispatchLoading, getDispatchLoading, getDispatchCommand, requireDispatchVersion, completeOutboundTask, getDispatchTask, getOutboundTask,
|
|
listDispatchTasks, listOutboundTasks, normalizeDispatchTask, normalizeOutboundTask,
|
|
pickDispatchItem, pickOutboundPallet, pickOutboundTask, resolveOutboundTask, outboundPickPayload
|
|
} from '../src/api/fulfillment.js'
|
|
import { session } from '../src/stores/session.js'
|
|
import { toDispatchError } from '../src/utils/dispatch-error.js'
|
|
|
|
test('dispatch network failures show recovery guidance and retain diagnostics', () => {
|
|
const error = toDispatchError({ message: 'request:fail', network: true, code: 'NETWORK_ERROR', requestId: 'network-request', traceId: 'network-trace' })
|
|
assert.equal(error.message, '网络连接失败,请检查网络后重试,当前作业已保留')
|
|
assert.equal(error.network, true)
|
|
assert.equal(error.code, 'NETWORK_ERROR')
|
|
assert.equal(error.requestId, 'network-request')
|
|
assert.equal(error.traceId, 'network-trace')
|
|
assert.equal(toDispatchError({ message: '业务校验失败' }).message, '业务校验失败')
|
|
})
|
|
|
|
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
|
|
|
|
test('outbound list and detail lead with the outbound number and label the task number separately', async () => {
|
|
const page = await read('../src/pages/outbound/outbound.vue')
|
|
assert.match(page, /class="business-code">\{\{ task\.outboundNo \|\| '—' \}\}/)
|
|
assert.match(page, /class="business-code">\{\{ current\.outboundNo \|\| '—' \}\}/)
|
|
assert.match(page, /任务号 \{\{ task\.taskNo \|\| '—' \}\}/)
|
|
assert.match(page, /任务号 \{\{ current\.taskNo \|\| '—' \}\}/)
|
|
assert.match(page, /@tap="openTask\(task\.taskNo\)"/)
|
|
})
|
|
|
|
test('native dispatch versions preserve explicit zero and reject missing or invalid versions', () => {
|
|
assert.equal(requireDispatchVersion(0), 0)
|
|
assert.equal(requireDispatchVersion('9007199254740993'), '9007199254740993')
|
|
for (const value of [undefined, null, -1, '-1', '', '1.1', 1.1, Number.MAX_SAFE_INTEGER + 1]) assert.throws(() => requireDispatchVersion(value), /版本/)
|
|
})
|
|
|
|
test('outbound partial pick submits the selected carton and distinguishes shared-pallet items', async () => {
|
|
const first = outboundPickPayload({ outboundPalletItemId: '11' }, { inventoryPositionId: '21', cartonMark: 'BOX-A' }, '1')
|
|
assert.deepEqual(first, { outboundPalletItemId: '11', inventoryPositionId: '21', packages: 1, cartonMarks: ['BOX-A'] })
|
|
assert.notDeepEqual(first, outboundPickPayload({ outboundPalletItemId: '12' }, { inventoryPositionId: '22', cartonMark: 'BOX-B' }, 1))
|
|
assert.deepEqual(outboundPickPayload({ outboundPalletItemId: '13' }, { inventoryPositionId: '23' }, 2).cartonMarks, [])
|
|
const page = await read('../src/pages/outbound/outbound.vue')
|
|
assert.match(page, /listOutboundTasks\('PENDING'\)/)
|
|
assert.match(page, /:key="item.displayKey \|\| item.outboundPalletItemId"/)
|
|
assert.match(page, /outboundPickPayload\(this.selectedItem, this.selectedPosition, this.packages, this.selectedScanCode\)/)
|
|
assert.match(page, /pickOutboundTask\(this.current.taskNo, \{ \.\.\.payload, idempotencyKey: key.value \}\)/)
|
|
assert.match(page, /pickOutboundPallet\(this.current.taskNo, code, \{ idempotencyKey: key.value \}\)/)
|
|
assert.match(page, /item\.palletNo === code/)
|
|
})
|
|
|
|
test('return outbound preserves its task type and only permits pallet scans for pallet items', async () => {
|
|
const task = normalizeOutboundTask({ task: { taskNo: 'PDA-1', outboundType: 'RETURN_OUTBOUND' }, items: [] })
|
|
assert.equal(task.outboundType, 'RETURN_OUTBOUND')
|
|
const page = await read('../src/pages/outbound/outbound.vue')
|
|
assert.match(page, /this\.current\?\.outboundType === 'RETURN_OUTBOUND' && item\.palletNo/)
|
|
assert.match(page, /整托出库请扫描托盘号/)
|
|
const item = { outboundPalletItemId: '12', skuCode: 'SKU-A' }
|
|
const position = { inventoryPositionId: '', cartonMark: 'RC-1' }
|
|
assert.deepEqual(outboundPickPayload(item, position, 1, 'SKU-A'), {
|
|
outboundPalletItemId: '12', inventoryPositionId: null, packages: 1, cartonMarks: [], skuCode: 'SKU-A'
|
|
})
|
|
assert.deepEqual(outboundPickPayload(item, position, 1, 'RC-1'), {
|
|
outboundPalletItemId: '12', inventoryPositionId: null, packages: 1, cartonMarks: ['RC-1']
|
|
})
|
|
})
|
|
|
|
test('real return-carton DTO remains scannable without inventing a warehouse inventory position or pallet', () => {
|
|
const task = normalizeOutboundTask({ task: { taskNo: 'PDA-RETURN', outboundType: 'RETURN_OUTBOUND', status: 'PENDING' },
|
|
items: [{ outboundPalletItemId: '12', palletNo: null, packageCount: 1, pickedPackageCount: 0,
|
|
positions: [{ inventoryPositionId: null, palletNo: null, cartonMark: 'RC-REAL', inboundCode: 'RT-REAL', availablePackages: 1, pickedPackages: 0 }] }] })
|
|
const item = task.items[0], position = item.positions[0]
|
|
assert.equal(item.palletNo, '')
|
|
assert.equal(position.cartonMark, 'RC-REAL')
|
|
assert.equal(position.availablePackages, 1)
|
|
assert.deepEqual(outboundPickPayload(item, position, 1, 'RC-REAL'), {
|
|
outboundPalletItemId: '12', inventoryPositionId: null, packages: 1, cartonMarks: ['RC-REAL']
|
|
})
|
|
assert.equal(outboundPickPayload(item, { ...position, inventoryPositionId: '2099999999999999999' }, 1).inventoryPositionId, '2099999999999999999')
|
|
})
|
|
|
|
function storageMock() {
|
|
const values = new Map()
|
|
globalThis.uni = {
|
|
setStorageSync(key, value) { values.set(key, structuredClone(value)) },
|
|
getStorageSync(key) { return structuredClone(values.get(key)) },
|
|
removeStorageSync(key) { values.delete(key) }
|
|
}
|
|
return values
|
|
}
|
|
|
|
test('phase-five workbench registers only dispatch and outbound PDA delivery boundaries', async () => {
|
|
const [workbench, pages] = await Promise.all([read('../src/data/overseas-workbench.js'), read('../src/pages.json')])
|
|
assert.match(workbench, /name: '出库履约'/)
|
|
assert.match(workbench, /key: 'dispatchPicking'[\s\S]*available: true[\s\S]*route: '\/pages\/dispatch\/dispatch'/)
|
|
assert.match(workbench, /key: 'outboundPicking'[\s\S]*available: true[\s\S]*route: '\/pages\/outbound\/outbound'/)
|
|
assert.match(pages, /pages\/dispatch\/dispatch/)
|
|
assert.match(pages, /pages\/outbound\/outbound/)
|
|
for (const excluded of ['派送单', '派送状态', '客户回访']) assert.doesNotMatch(workbench, new RegExp(excluded))
|
|
})
|
|
|
|
test('fulfillment API uses dedicated PDA routes and never sends customer or warehouse scope', async () => {
|
|
const api = await read('../src/api/fulfillment.js')
|
|
for (const path of ['dispatches', 'outbound-tasks', '/pick', '/loading-complete', '/loading-review']) assert.match(api, new RegExp(path.replace('/', '\\/')))
|
|
for (const forbidden of ['customerId', 'warehouseId', 'locationId']) assert.doesNotMatch(api, new RegExp(forbidden))
|
|
assert.match(api, /encodeURIComponent/)
|
|
})
|
|
|
|
test('fulfillment API calls the frozen list detail pick complete and review routes with exact payloads', async () => {
|
|
const calls = []
|
|
session.apiBase = '/api'; session.token = 'overseas-token'
|
|
globalThis.uni = {
|
|
request(options) {
|
|
calls.push({ method: options.method, url: options.url, data: options.data, authorization: options.header.Authorization })
|
|
let data = {}
|
|
if (options.method === 'GET' && /\/dispatches(?:\?|$)/.test(options.url)) data = []
|
|
else if (options.method === 'GET' && /\/outbound-tasks(?:\?|$)/.test(options.url)) data = []
|
|
else if (options.method === 'GET' && options.url.includes('/dispatches/')) data = { dispatchNo: 'DSP-1', taskNo: 'TASK-1', items: [] }
|
|
else if (options.method === 'GET' && options.url.includes('/outbound-tasks/resolve/')) data = { workflow: 'DISPATCH', dispatchNo: 'DSP-1', outboundNo: 'OUT-1' }
|
|
else if (options.method === 'GET' && options.url.includes('/outbound-tasks/')) data = { task: { taskNo: 'OUT-TASK-1', outboundNo: 'OUT-1' }, items: [] }
|
|
options.success({ statusCode: 200, data: { code: '200', data }, header: {} })
|
|
}
|
|
}
|
|
|
|
await listDispatchTasks(); await getDispatchTask('DSP/1')
|
|
await pickDispatchItem('DSP/1', '11', { recordVersion: 0, positionId: '22', packages: 1, productQuantity: 0, idempotencyKey: 'pick-key' })
|
|
await getDispatchLoading('DSP/1'); await getDispatchCommand('DSP/1', 'pick/key'); await completeDispatchLoading('DSP/1', { recordVersion: 1, idempotencyKey: 'complete-key', manifestHash: 'hash' })
|
|
await reviewDispatchLoading('DSP/1', { recordVersion: 0, idempotencyKey: 'review-key', decision: 'REJECTED', reason: '车牌不符' })
|
|
await listOutboundTasks('PENDING'); await getOutboundTask('OUT/TASK/1')
|
|
await resolveOutboundTask('OUT/1')
|
|
await pickOutboundTask('OUT/TASK/1', { outboundPalletItemId: '33', inventoryPositionId: '44', packages: 1, idempotencyKey: 'out-pick-key' })
|
|
await pickOutboundPallet('OUT/TASK/1', 'DP/1', { idempotencyKey: 'out-pallet-key' })
|
|
await completeOutboundTask('OUT/TASK/1', { idempotencyKey: 'out-complete-key' })
|
|
|
|
assert.equal(calls.length, 13)
|
|
assert.deepEqual(calls.find(call => call.url.endsWith('/dispatches/DSP%2F1/loading-review')).data, { recordVersion: 0, idempotencyKey: 'review-key', decision: 'REJECTED', reason: '车牌不符' })
|
|
assert.ok(calls.some((call) => call.url.endsWith('/dispatches')))
|
|
assert.ok(calls.some((call) => call.url.includes('/dispatches/DSP%2F1/items/11/pick')))
|
|
assert.ok(calls.some((call) => call.url.includes('/outbound-tasks/OUT%2FTASK%2F1/pick')))
|
|
assert.ok(calls.some((call) => call.url.includes('/outbound-tasks/OUT%2FTASK%2F1/pallets/DP%2F1/pick')))
|
|
assert.ok(calls.some((call) => call.url.endsWith('/outbound-tasks/resolve/OUT%2F1')))
|
|
assert.ok(calls.every((call) => call.authorization === 'Bearer overseas-token'))
|
|
assert.ok(calls.every((call) => !/[?&](?:customerId|warehouseId)=/.test(call.url)))
|
|
for (const call of calls) {
|
|
assert.equal(Object.hasOwn(call.data || {}, 'customerId'), false)
|
|
assert.equal(Object.hasOwn(call.data || {}, 'warehouseId'), false)
|
|
}
|
|
const dispatchComplete = calls.find((call) => call.url.endsWith('/dispatches/DSP%2F1/loading-complete'))
|
|
assert.deepEqual(dispatchComplete.data, { recordVersion: 1, idempotencyKey: 'complete-key', manifestHash: 'hash' }); assert.ok(calls.some(call => call.url.endsWith('/commands/pick%2Fkey')))
|
|
})
|
|
|
|
test('dispatch and outbound adapters retain stable business identifiers and count progress', () => {
|
|
const dispatch = normalizeDispatchTask({ taskNo: 'DPT-1', dispatchNo: 'DSP-1', actions: { LOADING_COMPLETE: { allowed: false, blockReason: '请先完成真实拣货' } }, items: [{ dispatchItemId: 8, palletNo: 'PLT-1', scheduledPackages: 5, pickedPackages: 2, positions: [{ positionId: 9, availablePackages: 5, allocatedPackages: 5, pickedPackages: 2 }] }] })
|
|
assert.equal(dispatch.dispatchNo, 'DSP-1')
|
|
assert.equal(dispatch.items[0].palletNo, 'PLT-1')
|
|
assert.equal(dispatch.items[0].positions[0].availablePackages, 5)
|
|
assert.equal(dispatch.actions.LOADING_COMPLETE.allowed, false)
|
|
assert.equal(dispatch.actions.LOADING_COMPLETE.blockReason, '请先完成真实拣货')
|
|
|
|
const outbound = normalizeOutboundTask({ task: { taskNo: 'OUT-TASK-1', outboundNo: 'OUT-1', status: 'PENDING' }, items: [{ outboundPalletItemId: 3, palletNo: 'PLT-2', skuCode: 'SKU-1', inboundCode: 'INB-1', packageCount: 4, pickedPackageCount: 1, status: 'PARTIAL_PICKED', positions: [{ inventoryPositionId: 7, palletNo: 'PLT-2', cartonMark: 'BOX-1', skuCode: 'SKU-1', inboundCode: 'INB-1', availablePackages: 3, pickedPackages: 1 }] }] })
|
|
assert.equal(outbound.taskNo, 'OUT-TASK-1')
|
|
assert.equal(outbound.items[0].palletNo, 'PLT-2')
|
|
assert.equal(outbound.items[0].outboundPalletItemId, '3')
|
|
assert.equal(outbound.items[0].plannedPackages, 4)
|
|
assert.equal(outbound.items[0].pickedPackages, 1)
|
|
assert.equal(outbound.items[0].positions[0].cartonMark, 'BOX-1')
|
|
assert.equal(outbound.items[0].positions[0].availablePackages, 3)
|
|
})
|
|
|
|
test('outbound draft restoration discards request keys and stays scoped to operator and warehouse', () => {
|
|
storageMock(); resetFulfillmentSession(); restoreFulfillmentDraft('operator-a:warehouse-a')
|
|
updateFulfillmentDraft('outbound', { currentTaskNo: 'OUT-001' })
|
|
const first = fulfillmentIdempotencyKey('outbound', 'pick', { taskNo: 'OUT-001', packages: 3 })
|
|
assert.notEqual(fulfillmentIdempotencyKey('outbound', 'pick', { taskNo: 'OUT-001', packages: 3 }).value, first.value)
|
|
fulfillment.outbound.currentTaskNo = ''; assert.equal(restoreFulfillmentDraft('operator-a:warehouse-a'), true)
|
|
assert.equal(fulfillment.outbound.currentTaskNo, 'OUT-001')
|
|
assert.notEqual(fulfillmentIdempotencyKey('outbound', 'pick', { taskNo: 'OUT-001', packages: 3 }).value, first.value)
|
|
assert.equal(restoreFulfillmentDraft('operator-b:warehouse-a'), false); assert.equal(fulfillment.outbound.currentTaskNo, '')
|
|
assert.equal('pendingKeys' in fulfillment.outbound, false)
|
|
})
|
|
|
|
test('pages retain loading, offline, diagnostics and native loading completion followed by shared loading documents', async () => {
|
|
const [dispatch, outbound] = await Promise.all([read('../src/pages/dispatch/dispatch.vue'), read('../src/pages/outbound/outbound.vue')])
|
|
for (const page of [dispatch,outbound]) {
|
|
for (const text of ['startScanBroadcast', '正在加载', '暂无', 'requestId', 'traceId', '返回列表']) assert.ok(page.includes(text))
|
|
assert.doesNotMatch(page, /模拟成功|静默成功|customerId:|warehouseId:|locationId:/)
|
|
}
|
|
assert.doesNotMatch(dispatch, /departDispatch|canDepart|确认整车发车|等待后台安排派送/); assert.match(dispatch, /DispatchLoadingDocuments/); assert.match(dispatch, /completeDispatchLoading/); assert.match(dispatch, /pickingReconciled === true/)
|
|
assert.doesNotMatch(dispatch, /beginDispatchCommand|pendingCommand/); assert.match(dispatch, /submitCommand/)
|
|
assert.doesNotMatch(dispatch, /tripDepartureRequest|completeDispatchPicking|fulfillmentIdempotencyKey/)
|
|
assert.match(outbound, /resolveOutboundTask[\s\S]*workflow === 'DISPATCH'[\s\S]*pages\/dispatch\/dispatch/)
|
|
assert.match(outbound, /outboundPickPayload/); assert.match(outbound, /await getOutboundTask/); assert.doesNotMatch(outbound, /releaseFulfillmentKey/)
|
|
assert.doesNotMatch(dispatch + outbound, /DeliveryOrder|deliveryStatus|customerVisit|\/deliveries/)
|
|
})
|
|
|
|
test('fulfillment workbench entries open task lists before details and keep explicit cross-workflow resume', async () => {
|
|
const [dispatch, outbound] = await Promise.all([read('../src/pages/dispatch/dispatch.vue'), read('../src/pages/outbound/outbound.vue')])
|
|
assert.match(dispatch, /resumeOnEntry: false/)
|
|
assert.match(dispatch, /onLoad\(options\) \{ this\.resumeOnEntry = options\?\.resume === '1' \}/)
|
|
assert.match(dispatch, /if \(this\.resumeOnEntry && fulfillment\.dispatch\.currentNo\) this\.openTask/)
|
|
assert.match(outbound, /if \(!this\.bootstrapped\) \{ this\.bootstrapped = true; this\.scanCode = ''; this\.loadTasks\(\); return \}/)
|
|
assert.match(outbound, /pages\/dispatch\/dispatch\?resume=1/)
|
|
for (const page of [dispatch, outbound]) {
|
|
assert.match(page, /detail-toolbar/)
|
|
assert.match(page, /上次作业/)
|
|
assert.match(page, /返回列表/)
|
|
}
|
|
})
|
|
|
|
test('dispatch vehicle-plate conflict uses actionable Chinese copy and preserves diagnostics', () => {
|
|
const error = toDispatchError({
|
|
code: 'A0409',
|
|
message: 'Vehicle plate is required before PDA push or picking',
|
|
requestId: 'faccb2ee-5e05-4904-9bef-bc983638b47b',
|
|
traceId: '43ca73abd84fab3e2992aa50e48fb48f'
|
|
}, '提交排车拣货失败')
|
|
assert.deepEqual(error, {
|
|
message: '请先在平台维护车牌信息后再拣货',
|
|
code: 'A0409',
|
|
requestId: 'faccb2ee-5e05-4904-9bef-bc983638b47b',
|
|
traceId: '43ca73abd84fab3e2992aa50e48fb48f',
|
|
network: false
|
|
})
|
|
|
|
const unknown = toDispatchError({ code: 'A0409', message: 'Another dispatch conflict', requestId: 'request-2' }, '失败')
|
|
assert.equal(unknown.message, 'Another dispatch conflict')
|
|
assert.equal(unknown.requestId, 'request-2')
|
|
assert.equal(toDispatchError({ code: 'A0403', message: 'Forbidden' }).message, '当前账号没有海外仓 PDA 作业权限,请联系管理员授权')
|
|
})
|
|
|
|
test('receiving conflict recovery keeps request and trace evidence visible on narrow screens', async () => {
|
|
const operation = await read('../src/pages/operation/operation.vue')
|
|
assert.match(operation, /openContainer\(candidate\)[\s\S]*recoverCurrentAfterConflict\(error, true\)/)
|
|
assert.match(operation, /\.error-card text\{overflow-wrap:anywhere;word-break:break-word\}/)
|
|
})
|
|
|
|
test('native action gates reject strings and retain server block reasons', () => {
|
|
for (const allowed of [true, false, undefined, 'true']) {
|
|
const task=normalizeDispatchTask({dispatchNo:'DS-1',items:[],actions:{PICK:{allowed,blockReason:'审核未通过'}}})
|
|
assert.equal(task.actions.PICK.allowed,allowed===true); assert.equal(task.actions.PICK.blockReason,'审核未通过')
|
|
}
|
|
})
|
|
|
|
async function dispatchScanHarness(items) {
|
|
const source = (await read('../src/pages/dispatch/dispatch.vue')).match(/<script>([\s\S]*?)<\/script>/)[1]
|
|
const draft={palletCode:'OLD',dispatchItemId:'old',cartonCode:''};let sounds=0
|
|
const deps={PortalHeader:{},ScanTargetInput:{},DispatchHandoverStatus:{},DispatchLoadingDocuments:{},fulfillment:{dispatch:draft},updateFulfillmentDraft:(_,value)=>Object.assign(draft,value),playSuccessSound:()=>{sounds++}}
|
|
const definition=new Function(...Object.keys(deps),source.replace(/^\s*import .*$/gm,'').replace('export default','return'))(...Object.values(deps))
|
|
const state={activeTab:'picking',current:{dispatchNo:'DS-TEST',stage:'LOADING',actions:{PICK:{allowed:true}},items:items.map(item=>({sourceKey:`source-${item.dispatchItemId}`,sourceBusinessType:'RELABEL_ORDER',sourceBusinessNo:`RL-${item.dispatchItemId}`,positions:[],...item,returnCartons:item.returnCartons||[]}))},error:{message:'previous error'},successMessage:'',setError(error){this.error={message:error.message}}}
|
|
state.gate=definition.methods.gate.bind(state);state.selectPallet=definition.methods.selectPallet.bind(state);state.chooseSource=definition.methods.chooseSource.bind(state)
|
|
return {state,draft,scan:definition.methods.handleScan.bind(state),sounds:()=>sounds}
|
|
}
|
|
|
|
test('dispatch wrong scan clears prior selection and a valid pallet clears the old error', async () => {
|
|
const h = await dispatchScanHarness([{ palletNo: 'PALLET-A', dispatchItemId: 'a', scheduledPackages: 1, pickedPackages: 0, positions: [{ availablePackages: 1 }] }])
|
|
h.scan('UNKNOWN'); assert.equal(h.draft.dispatchItemId, ''); assert.ok(h.state.error)
|
|
h.scan('PALLET-A'); assert.equal(h.state.error, null); assert.equal(h.draft.dispatchItemId, 'a'); assert.equal(h.sounds(), 1)
|
|
})
|
|
|
|
test('dispatch cannot use a shared destination code to choose a physical pallet', async () => {
|
|
const h = await dispatchScanHarness(['A', 'B'].map(x => ({ palletNo: `PALLET-${x}`, fbaWarehouseCode: 'ONT8', dispatchItemId: x, scheduledPackages: 1, pickedPackages: 0 })))
|
|
h.scan('ONT8'); assert.equal(h.draft.dispatchItemId, ''); assert.ok(h.state.error); assert.equal(h.sounds(), 0)
|
|
})
|
|
|
|
test('dispatch completed or ambiguous pallet scan never silently selects a row', async () => {
|
|
const done = await dispatchScanHarness([{ palletNo: 'PALLET-A', dispatchItemId: 'a', scheduledPackages: 1, pickedPackages: 1 }])
|
|
done.scan('PALLET-A'); assert.match(done.state.error.message, /已完成拣货/); assert.equal(done.draft.dispatchItemId, ''); assert.equal(done.sounds(), 0)
|
|
const shared = await dispatchScanHarness(['A','B'].map(x => ({ palletNo: 'PALLET-SHARED', dispatchItemId: x, scheduledPackages: 1, pickedPackages: 0 })))
|
|
shared.scan('PALLET-SHARED'); assert.match(shared.state.error.message, /多条待拣明细/); assert.equal(shared.draft.dispatchItemId, ''); assert.equal(shared.sounds(), 0)
|
|
})
|
|
|
|
test('single SKU piece plan is preserved for packed carton and loose SKU scans', () => {
|
|
const item={outboundPalletItemId:'12',returnSkuQuantity:'2'};
|
|
const pos={cartonMark:'RC-1',inventoryPositionId:null};
|
|
assert.deepEqual(outboundPickPayload(item,pos,1,'RC-1'),{outboundPalletItemId:'12',inventoryPositionId:null,packages:1,productQuantity:2,cartonMarks:['RC-1']});
|
|
assert.deepEqual(outboundPickPayload({...item,skuCode:'SKU-A'},pos,1,'SKU-A'),{outboundPalletItemId:'12',inventoryPositionId:null,packages:1,productQuantity:2,cartonMarks:[],skuCode:'SKU-A'});
|
|
assert.equal('productQuantity' in outboundPickPayload({outboundPalletItemId:'12'},pos,1),false);
|
|
});
|