优化
This commit is contained in:
@@ -4,7 +4,7 @@ import { readFile } from 'node:fs/promises'
|
||||
import { fulfillment, beginDispatchCommand, patchDispatchCommand, finishDispatchCommand, discardRejectedDispatchCommand, restoreFulfillmentDraft, updateFulfillmentDraft, resetFulfillmentSession } from '../src/stores/fulfillment.js'
|
||||
import { requireDispatchVersion, dispatchVersionAtLeast, normalizeDispatchTask } from '../src/api/fulfillment.js'
|
||||
const source = (await readFile(new URL('../src/pages/dispatch/dispatch.vue', import.meta.url), 'utf8')).match(/<script>([\s\S]*?)<\/script>/)[1]
|
||||
const item = (overrides = {}) => ({ dispatchItemId: 'item-1', customerId: 'customer-1', customerName: '客户甲', palletNo: 'PALLET-1', scheduledPackages: 2, pickedPackages: 0, positions: [{ positionId: 'position-1', availablePackages: 2, allocatedPackages: 2, pickedPackages: 0 }], returnCartons: [], ...overrides })
|
||||
const item = (overrides = {}) => ({ dispatchItemId: 'item-1', sourceKey: 'source-1', sourceBusinessType: 'RELABEL_ORDER', sourceBusinessNo: 'RL-1', customerId: 'customer-1', customerName: '客户甲', palletNo: 'PALLET-1', scheduledPackages: 2, pickedPackages: 0, positions: [{ positionId: 'position-1', availablePackages: 2, allocatedPackages: 2, pickedPackages: 0 }], returnCartons: [], ...overrides })
|
||||
test('native string quantities normalize without changing Long identity or version', () => {
|
||||
const id = '9007199254740993'
|
||||
const value = normalizeDispatchTask(JSON.parse(JSON.stringify({ dispatchNo: 'PC/1', recordVersion: id, items: [item({ dispatchItemId: id, scheduledPackages: '12', pickedPackages: '0', positions: [{ positionId: id, balanceId: id, availablePackages: '12', allocatedPackages: '12', pickedPackages: '0' }], returnCartons: [{ returnCartonId: id, productQuantity: '24' }] })] })))
|
||||
@@ -101,3 +101,54 @@ test('position picking is bounded by this dispatch allocation and skips already
|
||||
state.selectPallet(state.current.items[0]); assert.equal(state.selectedPosition.positionId, 'actual-position'); assert.equal(state.selectedRemaining, 1)
|
||||
updateFulfillmentDraft('dispatch', { packages: 2 }); assert.equal(state.canPick, false)
|
||||
})
|
||||
test('a shared pallet exposes both relabel sources and picking the second preserves its exact position and quantity', async () => {
|
||||
storage(); const rows = [item({ scheduledPackages: 3 }), item({ dispatchItemId: 'item-2', sourceKey: 'source-2', sourceBusinessNo: 'RL-2', customerId: 'customer-2', customerName: '客户乙', scheduledPackages: 5, positions: [{ positionId: 'position-2', availablePackages: 5, allocatedPackages: 5, pickedPackages: 0 }] })]
|
||||
let sent
|
||||
const state = page({ pickDispatchItem: async (...args) => { sent = args; return proof(fulfillment.dispatch.pendingCommand) }, getDispatchTask: async () => task({ recordVersion: 1, items: rows.map((row, index) => index ? { ...row, pickedPackages: 5 } : row) }) })
|
||||
state.current = task({ items: rows }); state.handleScan('PALLET-1')
|
||||
assert.equal(fulfillment.dispatch.dispatchItemId, ''); assert.deepEqual(state.sourceChoices.map(row => [row.sourceBusinessNo, row.customerId, state.pickablePackages(row)]), [['RL-1', 'customer-1', 2], ['RL-2', 'customer-2', 5]])
|
||||
state.chooseSource(state.sourceChoices[1]); state.updatePackages({ detail: { value: '5' } }); await state.submitPick()
|
||||
assert.equal(sent[1], 'item-2'); assert.equal(sent[2].positionId, 'position-2'); assert.equal(sent[2].packages, 5); assert.equal(state.canComplete, false)
|
||||
})
|
||||
test('an ambiguous source carton requires explicit source selection and never picks the first matching item', async () => {
|
||||
storage(); let sent
|
||||
const rows = [item({ returnCartons: [{ returnCartonId: 'carton-1', cartonMark: 'SHARED-MARK', status: 'OUTBOUND_PENDING', productQuantity: 2 }] }), item({ dispatchItemId: 'item-2', sourceKey: 'source-2', sourceBusinessNo: 'RL-2', customerId: 'customer-2', returnCartons: [{ returnCartonId: 'carton-2', cartonMark: 'SHARED-MARK', status: 'OUTBOUND_PENDING', productQuantity: 7 }] })]
|
||||
const state = page({ pickDispatchItem: async (...args) => { sent = args; return proof(fulfillment.dispatch.pendingCommand) }, getDispatchTask: async () => task({ recordVersion: 1, items: rows }) })
|
||||
state.current = task({ items: rows }); state.handleScan('SHARED-MARK')
|
||||
assert.equal(state.sourceChoices.length, 2); assert.equal(fulfillment.dispatch.dispatchItemId, ''); assert.equal(state.canPick, false)
|
||||
state.chooseSource(state.sourceChoices[1]); await state.submitPick()
|
||||
assert.equal(sent[1], 'item-2'); assert.equal(sent[2].cartonMark, 'SHARED-MARK'); assert.equal(sent[2].productQuantity, 7); assert.equal(sent[2].positionId, null)
|
||||
})
|
||||
test('selection restoration cannot reuse the same item ID for a different logical source', () => {
|
||||
storage(); const state = page(); state.selectPallet(state.current.items[0]); assert.equal(state.canPick, true)
|
||||
state.current.items[0].sourceKey = 'different-source'; assert.equal(state.selectedItem, null); assert.equal(state.canPick, false)
|
||||
})
|
||||
for (const sourceType of ['INBOUND_PLAN', 'RETURN_RECEIPT']) test(`ordinary ${sourceType} null business fields survive real wire normalization and exact picking`, async () => {
|
||||
storage(); let sent
|
||||
const raw = JSON.parse(JSON.stringify({ dispatchNo: 'PC/1', taskNo: 'TASK-1', recordVersion: '0', status: 'ACTIVE', stage: 'LOADING', actions: { PICK: { allowed: true } }, pickingReconciled: false, items: [item({ sourceKey: `${sourceType}:source-1`, sourceBusinessType: null, sourceBusinessNo: null, scheduledPackages: '4', pickedPackages: '0', positions: [{ positionId: '9007199254740993', balanceId: '9007199254740994', availablePackages: '4', allocatedPackages: '4', pickedPackages: '0' }] })] }))
|
||||
const normalized = normalizeDispatchTask(raw)
|
||||
assert.equal(normalized.items[0].sourceBusinessType, null); assert.equal(normalized.items[0].sourceBusinessNo, null)
|
||||
const state = page({ pickDispatchItem: async (...args) => { sent = args; return proof(fulfillment.dispatch.pendingCommand) }, getDispatchTask: async () => normalizeDispatchTask({ ...raw, recordVersion: 1 }) })
|
||||
state.current = normalized; state.handleScan('PALLET-1'); assert.equal(state.canPick, true)
|
||||
state.updatePackages({ detail: { value: '4' } }); await state.submitPick()
|
||||
assert.equal(sent[1], 'item-1'); assert.equal(sent[2].positionId, '9007199254740993'); assert.equal(sent[2].packages, 4); assert.equal(sent[2].recordVersion, '0')
|
||||
assert.equal('customerId' in sent[2], false)
|
||||
})
|
||||
test('RELABEL_ORDER still requires its true business number and all sources require sourceKey', () => {
|
||||
storage(); const state = page()
|
||||
state.selectPallet(item({ sourceBusinessNo: null })); assert.equal(fulfillment.dispatch.dispatchItemId, ''); assert.match(state.error.message, /来源业务身份/)
|
||||
state.selectPallet(item({ sourceKey: '', sourceBusinessType: null, sourceBusinessNo: null })); assert.equal(fulfillment.dispatch.dispatchItemId, '')
|
||||
})
|
||||
test('duplicate matching carton facts inside one source fail closed rather than taking its first carton', () => {
|
||||
storage(); const state = page()
|
||||
state.current = task({ items: [item({ returnCartons: ['one', 'two'].map(returnCartonId => ({ returnCartonId, cartonMark: 'DUPLICATE', status: 'OUTBOUND_PENDING', productQuantity: 1 })) })] })
|
||||
state.handleScan('DUPLICATE'); assert.match(state.error.message, /重复待拣箱唛/); assert.equal(fulfillment.dispatch.dispatchItemId, ''); assert.equal(state.canPick, false)
|
||||
})
|
||||
test('two picked logical sources use the single authoritative physical loading row without duplicating measurements', async () => {
|
||||
storage(); const rows = [item({ pickedPackages: 2 }), item({ dispatchItemId: 'item-2', sourceKey: 'source-2', sourceBusinessNo: 'RL-2', pickedPackages: 2 })]
|
||||
const preview = { dispatchNo: 'PC/1', recordVersion: 0, manifestHash: 'one-physical-pallet', pickingReconciled: true, loadingComplete: { allowed: true }, pallets: [{ warehousePalletId: '21', palletNo: 'PALLET-1', palletSpaces: 1, deliveryIndex: 1, weightKg: 100, volumeCbm: 2, evidence: [{ field: 'weightKg', actual: true }, { field: 'volumeCbm', actual: true }] }] }
|
||||
let sent
|
||||
const state = page({ getDispatchLoading: async () => preview, completeDispatchLoading: async (...args) => { sent = args; return proof(fulfillment.dispatch.pendingCommand) }, getDispatchTask: async () => task({ recordVersion: 1, stage: 'AFTER_LOADING', items: rows, pickingReconciled: true }) })
|
||||
state.current = task({ items: rows, pickingReconciled: true }); await state.prepareLoading(); assert.equal(state.loadingRows.length, 1)
|
||||
await state.completeLoading(); assert.deepEqual(sent[1].measurements, [{ warehousePalletId: '21', palletSpaces: 1, weightKg: 100, volumeCbm: 2, deliveryIndex: 1 }])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user