优化
This commit is contained in:
@@ -61,7 +61,7 @@
|
||||
|
||||
## 海外业务接口边界
|
||||
|
||||
- 仓储转运按托盘分别保存箱数、长宽高(cm)和毛重(kg),体积按本托尺寸计算;短收/超收仅展示差异,不以预报箱数强制核平。生成托盘号后锁定该卡测量数据,必须取得服务端绑定确认且至少有一张现场照片才能完成打托;完成请求只提交稳定幂等键和照片文件 ID,不重复提交整份托盘测量。重新进入从详情恢复托盘和鉴权照片;标签使用服务端 `labelDownloadPath` 下载 PDF,打开文档不等于真机打印完成。
|
||||
- 仓储转运按托盘分别保存箱数、长宽高(cm)和毛重(kg),体积按本托尺寸计算;SKU件数逐种核对差异,不以预报箱数强制核平。箱唛沿用客户预报编号,PDA不得另造新箱号;测量完成前客户可增加预报箱数,PDA刷新时合并新箱并保留本机草稿,清点提交携带forecastPackageCount拦截旧快照。生成托盘号后锁定该卡测量数据,必须取得服务端绑定确认且至少有一张现场照片才能完成打托;完成请求提交稳定幂等键、照片文件 ID,SKU实收件数有差异时在完成入口弹窗必填差异原因,不重复提交整份托盘测量。重新进入从详情恢复托盘和鉴权照片;标签使用服务端 `labelDownloadPath` 下载 PDF,打开文档不等于真机打印完成。
|
||||
|
||||
- 换标目标确认即提交拆托合托,不再二次点击底部提交。`RelabelPalletWork.vue` 按当前轮次 `PALLET_BIND` 最新记录提供逐箱改绑,扫描新目标后弹窗确认,使用既有 `relabel-disassemble` 精确来源路径(不传 `actualSource`);同目标不发写请求。服务器持有库存、托盘版本与幂等约束,已排车或出库禁止直接改绑。离线回归 `tests/relabel-pallet-work.test.mjs`,不等同真机验收。
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ export function countWarehouseTransferCartons(transferNo, payload) {
|
||||
return overseasRequest('POST', `${BASE}/${encoded(transferNo)}/cartons/count`, payload)
|
||||
}
|
||||
|
||||
export function reopenWarehouseTransferMeasurement(transferNo) {
|
||||
return overseasRequest('POST', `${BASE}/${encoded(transferNo)}/cartons/reopen`)
|
||||
}
|
||||
|
||||
export function getWarehouseTransferCartonLabel(transferNo) {
|
||||
return overseasRequest('GET', `${BASE}/${encoded(transferNo)}/carton-label`)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<template><view><view class="measure-grid"><label><text>长(cm)</text><input v-model="box.length" type="digit" class="measure-input" :disabled="disabled" placeholder="长度" /></label><label><text>宽(cm)</text><input v-model="box.width" type="digit" class="measure-input" :disabled="disabled" placeholder="宽度" /></label><label><text>高(cm)</text><input v-model="box.height" type="digit" class="measure-input" :disabled="disabled" placeholder="高度" /></label><label><text>重量(kg)</text><input v-model="box.weight" type="digit" class="measure-input" :disabled="disabled" placeholder="重量" /></label></view><view class="volume-chip"><text>体积</text><strong>{{ boxVolume(box) }} cm³</strong></view>
|
||||
<view v-for="(line, lineIndex) in box.items" :key="lineIndex" class="count-line"><picker class="sku-picker" :disabled="disabled || !allowSkuChange || (lockFirstSku && lineIndex === 0)" :range="lineSkuCodes(lineIndex)" :value="Math.max(0, lineSkuCodes(lineIndex).indexOf(line.skuCode))" @change="line.skuCode = lineSkuCodes(lineIndex)[Number($event.detail.value)]"><text>{{ line.skuCode || '选择 SKU' }} ▾</text></picker><input v-model="line.quantity" type="number" class="number-input quantity" :disabled="disabled" placeholder="数量" /><button v-if="!disabled && allowSkuChange && box.items.length > 1 && (!lockFirstSku || lineIndex > 0)" class="link-button" :disabled="disabled" @tap="box.items.splice(lineIndex, 1)">删除</button></view></view></template>
|
||||
<template><view :class="{ 'compact-measure': volumeUnit === 'm3' }"><view class="measure-grid"><label><text>长(cm)</text><input v-model="box.length" type="digit" class="measure-input" :disabled="disabled" placeholder="长度" /></label><label><text>宽(cm)</text><input v-model="box.width" type="digit" class="measure-input" :disabled="disabled" placeholder="宽度" /></label><label><text>高(cm)</text><input v-model="box.height" type="digit" class="measure-input" :disabled="disabled" placeholder="高度" /></label><label><text>重量(kg)</text><input v-model="box.weight" type="digit" class="measure-input" :disabled="disabled" placeholder="重量" /></label></view><view class="volume-chip"><text>体积</text><strong>{{ boxVolume(box) }} {{ volumeUnit === 'm3' ? 'm³' : 'cm³' }}</strong></view>
|
||||
<view v-for="(line, lineIndex) in box.items" :key="lineIndex" class="count-line"><picker class="sku-picker" :disabled="disabled || !allowSkuChange || (lockFirstSku && lineIndex === 0)" :range="lineSkuCodes(lineIndex)" :value="Math.max(0, lineSkuCodes(lineIndex).indexOf(line.skuCode))" @change="line.skuCode = lineSkuCodes(lineIndex)[Number($event.detail.value)]"><text>{{ line.skuCode || '选择 SKU' }} ▾</text></picker><slot v-if="lineIndex === 0" name="sku-action" /><input v-model="line.quantity" type="number" class="number-input quantity" :disabled="disabled" placeholder="数量" /><button v-if="!disabled && allowSkuChange && box.items.length > 1 && (!lockFirstSku || lineIndex > 0)" class="link-button" :disabled="disabled" @tap="box.items.splice(lineIndex, 1)">删除</button></view></view></template>
|
||||
<script>
|
||||
export default {
|
||||
props: { box: { type: Object, required: true }, skuCodes: { type: Array, default: () => [] }, disabled: Boolean, uniqueSkus: Boolean, lockFirstSku: Boolean, allowSkuChange: { type: Boolean, default: true } },
|
||||
methods: { lineSkuCodes(index) { return this.uniqueSkus ? this.skuCodes.filter(sku => !this.box.items.some((line, other) => other !== index && line.skuCode === sku)) : this.skuCodes }, boxVolume(box) { const value = Number(box.length) * Number(box.width) * Number(box.height); return Number.isFinite(value) && value > 0 ? value.toFixed(2) : '—' } }
|
||||
props: { box: { type: Object, required: true }, skuCodes: { type: Array, default: () => [] }, volumeUnit: { type: String, default: 'cm3' }, disabled: Boolean, uniqueSkus: Boolean, lockFirstSku: Boolean, allowSkuChange: { type: Boolean, default: true } },
|
||||
methods: {
|
||||
// 混箱中同一 SKU 只出现一次,保留当前行已选择的 SKU。
|
||||
lineSkuCodes(index) { return this.uniqueSkus ? this.skuCodes.filter(sku => !this.box.items.some((line, other) => other !== index && line.skuCode === sku)) : this.skuCodes },
|
||||
// 转运按立方米显示三位;其他作业保留原来的立方厘米显示。
|
||||
boxVolume(box) { const value = Number(box.length) * Number(box.width) * Number(box.height); return Number.isFinite(value) && value > 0 ? (this.volumeUnit === 'm3' ? (value / 1000000).toFixed(3) : value.toFixed(2)) : '—' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.count-line{display:flex;align-items:center;gap:8px;margin-top:8px}.sku-picker{flex:1;min-width:0;font-size:12px;word-break:break-all}.quantity{box-sizing:border-box;flex:none;width:90px;height:34px;text-align:center;font-size:13px;border:1px solid #cbd9e8;border-radius:8px;background:#fbfdff}.link-button{width:auto;font-size:12px;color:#135ee8}
|
||||
.measure-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:7px;margin:10px 0 6px}.measure-grid label text{display:block;margin-bottom:4px;color:#65768b;font-size:10px}.measure-input{box-sizing:border-box;width:100%;height:34px;padding:0 8px;border:1px solid #cbd9e8;border-radius:8px;color:#203750;font-size:13px;text-align:center;background:#fbfdff}.volume-chip{display:flex;align-items:baseline;justify-content:flex-end;gap:4px;margin:4px 0 8px;color:#728197;font-size:10px}.volume-chip strong{color:#135ee8;font-size:14px}
|
||||
</style>
|
||||
.compact-measure .count-line{padding-top:8px;border-top:1px solid #edf1f6}.compact-measure .sku-picker{line-height:18px}.compact-measure .quantity{width:76px;font-weight:600;background:#fff}.compact-measure .measure-grid{gap:10px}.compact-measure .measure-input{height:38px;background:#f8fafd}.compact-measure .volume-chip{margin:8px 0;padding:6px 10px;border-radius:7px;background:#f4f8ff}.compact-measure .volume-chip strong{font-size:13px}</style>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -56,8 +56,8 @@ test('warehouse transfer removes completed work from the pending list', async ()
|
||||
|
||||
test('warehouse transfer shows forecast and actual variance and requires counted box reconciliation', async () => {
|
||||
const page = await readFile(new URL('../src/pages/warehouse-transfer/warehouse-transfer.vue', import.meta.url), 'utf8')
|
||||
for (const label of ['客户预报', '当前实收', '短收差异', '超收差异']) assert.match(page, new RegExp(label))
|
||||
assert.match(page, /cartonDifference/)
|
||||
for (const label of ['客户预报', 'SKU实收', 'SKU签收差异']) assert.match(page, new RegExp(label))
|
||||
assert.match(page, /hasSkuReceiptDifference/)
|
||||
assert.doesNotMatch(page, /全部托盘箱数必须等于订单计划箱数/)
|
||||
assert.doesNotMatch(page, /本托 SKU 箱数|itemCounts/)
|
||||
})
|
||||
@@ -137,7 +137,7 @@ test('warehouse transfer pallet cards prepare generated and server-bound states'
|
||||
assert.match(page, /handlePalletScan/)
|
||||
assert.match(page, /扫描或手输新托盘标签号/)
|
||||
assert.match(page, /已绑定实物托盘/)
|
||||
assert.match(page, /!pallet\.generated.*removePallet/)
|
||||
assert.match(page, /@tap\.stop="removePallet\(index\)">×/)
|
||||
assert.match(page, /请先生成托盘号,再填写签收箱子/)
|
||||
assert.match(page, /collapsed: false/)
|
||||
assert.match(page, /pallet\.collapsed = !pallet\.collapsed/)
|
||||
@@ -149,5 +149,5 @@ test('warehouse transfer pallet cards prepare generated and server-bound states'
|
||||
assert.match(page, /bindWarehouseTransferPallet/)
|
||||
assert.match(page, /deleteWarehouseTransferPallet/)
|
||||
assert.match(page, /请先生成并扫描确认绑定全部托盘/)
|
||||
assert.match(page, /const payload = \{ idempotencyKey: this\.pendingKey, photoFileIds: \[\.\.\.this\.photoFileIds\] \}/)
|
||||
assert.match(page, /const payload = \{ idempotencyKey: this\.pendingKey, photoFileIds: \[\.\.\.this\.photoFileIds\], discrepancyReason:/)
|
||||
})
|
||||
|
||||
@@ -11,18 +11,63 @@ function setup() {
|
||||
const value = scope.page.data()
|
||||
for(const [key,method] of Object.entries(scope.page.methods)) value[key]=method.bind(value)
|
||||
for(const [key,get] of Object.entries(scope.page.computed)) Object.defineProperty(value,key,{get:()=>get.call(value)})
|
||||
value.selected={transferNo:'WT1',packageCount:1,status:'WAIT_PDA_RECEIVING',items:[{id:'I1',skuCode:'A'}],cartons:[{cartonMark:'BOX1',transferItemId:'I1',status:'EXPECTED'}]}
|
||||
value.selected={transferNo:'WT1',packageCount:1,status:'WAIT_PDA_RECEIVING',items:[{id:'I1',skuCode:'A',quantity:4}],cartons:[{cartonMark:'BOX1',transferItemId:'I1',status:'EXPECTED'}]}
|
||||
value.pallets=[{key:'P1',draftId:'P1',palletNo:'PALLET1',generated:true,bound:false}];value.restoreCartons();Object.assign(value.cartonBoxes[0],{length:10,width:20,height:30,weight:2});value.cartonBoxes[0].items[0].quantity=4
|
||||
return {scope,value,storage}
|
||||
}
|
||||
test('summary toggle preserves measurements and volume uses three decimals',()=>{
|
||||
const {value}=setup()
|
||||
assert.equal(value.summaryCollapsed,false)
|
||||
value.summaryCollapsed=true
|
||||
assert.equal(value.cartonBoxes[0].weight,2)
|
||||
Object.assign(value.cartonBoxes[0],{length:10,width:10,height:6})
|
||||
value.addExtraCarton()
|
||||
Object.assign(value.cartonBoxes[1],{length:10,width:10,height:6,weight:2})
|
||||
assert.equal(value.palletVolume(value.pallets[0]),'0.001')
|
||||
assert.equal(value.totalVolume,'0.001')
|
||||
assert.match(source,/aria-label="添加 SKU"/)
|
||||
assert.match(source,/aria-label="删除箱子"/)
|
||||
})
|
||||
|
||||
test('remeasure preserves saved measurements and unlocks carton editing',async()=>{
|
||||
const {value,scope}=setup();
|
||||
const box={...value.cartonBoxes[0],countedAt:'2026-10-16',status:'COUNTED'};
|
||||
value.selected.cartons=[box]; value.cartonRequestKey='old-key';
|
||||
scope.reopenWarehouseTransferMeasurement=async()=>({...value.selected,cartons:[{...box,countedAt:null}]});
|
||||
await value.remeasure();
|
||||
assert.equal(value.cartonsCounted,false); assert.equal(value.cartonBoxes[0].weight,2);
|
||||
assert.equal(value.cartonBoxes[0].transferPalletId,'P1'); assert.equal(value.cartonRequestKey,'');
|
||||
assert.equal(value.cartonCountReady,true);
|
||||
})
|
||||
|
||||
test('bound pallet deletion retains box values and requires reassignment',async()=>{
|
||||
const {value,scope}=setup();
|
||||
scope.uni.showModal=options=>options.success({confirm:true});
|
||||
value.pallets[0].bound=true;
|
||||
scope.deleteWarehouseTransferPallet=async()=>({...value.selected,pallets:[]});
|
||||
await value.removePallet(0);
|
||||
assert.equal(value.cartonBoxes[0].transferPalletId,''); assert.equal(value.cartonBoxes[0].weight,2);
|
||||
assert.equal(value.generatedPallets.length,0); assert.equal(value.pallets.length,1);
|
||||
assert.equal(value.cartonCountReady,false); assert.equal(value.cartonPalletFilter,'');
|
||||
})
|
||||
|
||||
test('failed pallet deletion retains binding and measurements',async()=>{
|
||||
const {value,scope}=setup();
|
||||
scope.uni.showModal=options=>options.success({confirm:true});
|
||||
scope.deleteWarehouseTransferPallet=async()=>{throw new Error('offline')};
|
||||
await value.removePallet(0);
|
||||
assert.equal(value.cartonBoxes[0].transferPalletId,'P1'); assert.equal(value.pallets[0].busy,false);
|
||||
assert.equal(value.error.message,'offline');
|
||||
})
|
||||
|
||||
test('transfer counts require valid pieces and measurements and record variance reason',()=>{
|
||||
const {value}=setup();assert.equal(value.cartonCountReady,true)
|
||||
value.cartonBoxes[0].items[0].quantity=0;assert.equal(value.cartonCountReady,false)
|
||||
value.cartonBoxes[0].items[0].quantity=1.5;assert.equal(value.cartonCountReady,false)
|
||||
value.cartonBoxes[0].items[0].quantity=4;value.cartonBoxes[0].height=0;assert.equal(value.cartonCountReady,false)
|
||||
value.cartonBoxes[0].height=30;value.addExtraCarton();Object.assign(value.cartonBoxes[1],{length:10,width:20,height:30,weight:2});value.cartonBoxes[1].items[0].quantity=5
|
||||
assert.equal(value.cartonCountReady,false);value.countDiscrepancyReason='多一箱';assert.equal(value.cartonCountReady,true)
|
||||
assert.equal(value.cartonQuantity,9);assert.equal(value.cartonVolume,'0.012000')
|
||||
assert.equal(value.cartonCountReady,true);value.countDiscrepancyReason='多一箱';assert.equal(value.cartonCountReady,true)
|
||||
assert.equal(value.cartonQuantity,9);assert.equal(value.cartonVolume,'0.012')
|
||||
})
|
||||
test('network retry preserves request key and changed content uses a new request',async()=>{
|
||||
const {value,scope}=setup();const requests=[]
|
||||
@@ -32,11 +77,9 @@ test('network retry preserves request key and changed content uses a new request
|
||||
value.cartonBoxes[0].items[0].quantity=8;await value.confirmCartons();assert.notEqual(requests[1].idempotencyKey,requests[2].idempotencyKey)
|
||||
assert.equal(value.countingCartons,false);assert.equal(value.error.message,'offline')
|
||||
})
|
||||
test('draft restores additional cartons and discrepancy reason and scanning rejects foreign box',()=>{
|
||||
test('draft restores additional cartons and discrepancy reason without crossing tasks',()=>{
|
||||
const {value}=setup();value.addExtraCarton();value.countDiscrepancyReason='超收';value.saveCartonDraft();value.restoreCartons()
|
||||
assert.equal(value.cartonBoxes.length,2);assert.equal(value.countDiscrepancyReason,'超收')
|
||||
value.locateCarton('FOREIGN');assert.match(value.error.message,/不属于/)
|
||||
value.locateCarton('BOX1');assert.equal(value.selectedCartonMark,'BOX1');assert.equal(value.cartonScrollTarget,'transfer-box-0')
|
||||
value.selected={...value.selected,transferNo:'WT2'};value.restoreCartons();assert.equal(value.cartonBoxes.length,1)
|
||||
})
|
||||
test('received count gates pallet completion and saving cannot switch active order',()=>{
|
||||
@@ -47,6 +90,7 @@ test('received count gates pallet completion and saving cannot switch active ord
|
||||
|
||||
test('batch applies only selected received boxes of one SKU and preserves independent drafts',()=>{
|
||||
const {value}=setup()
|
||||
value.selected.items.push({id:'I2',skuCode:'B'})
|
||||
value.addExtraCarton();value.addExtraCarton();value.addExtraCarton()
|
||||
value.cartonBoxes[2].items[0].skuCode='B'
|
||||
value.cartonBoxes[3].received=false
|
||||
@@ -127,3 +171,83 @@ test('pallet number can be created before measurements and new boxes receive tha
|
||||
assert.equal(value.cartonBoxes[0].transferPalletId,'CREATED')
|
||||
assert.equal(value.pallets[0].palletNo,'NEWPALLET')
|
||||
})
|
||||
|
||||
// 最终完成入口需要区分无差异、取消、空白原因和网络重试。
|
||||
for (const answer of [{confirm:false}, {confirm:true,content:' '}, {confirm:true,content:'少到一箱'}]) {
|
||||
test('completion handles discrepancy answer '+JSON.stringify(answer),async()=>{
|
||||
const {value,scope}=setup();let requests=[];let prompts=0
|
||||
value.selected.items[0].quantity=5;value.selected.cartons[0].countedAt='2026-10-16'
|
||||
value.pallets[0].bound=true;value.photoFileIds=[91]
|
||||
scope.uni.showModal=options=>{prompts++;options.success(answer)}
|
||||
scope.palletizeWarehouseTransfer=async(no,payload)=>{requests.push(payload);throw new Error('offline')}
|
||||
await value.submit()
|
||||
assert.equal(prompts,1);assert.equal(value.submitting,false)
|
||||
assert.equal(requests.length,answer.confirm && answer.content?.trim()?1:0)
|
||||
if(requests.length){assert.equal(requests[0].discrepancyReason,'少到一箱');await value.submit();assert.equal(requests[0].idempotencyKey,requests[1].idempotencyKey)}
|
||||
})
|
||||
}
|
||||
test('matching SKU counts with different carton count submit without prompting, and missing photos block before prompting',async()=>{
|
||||
const {value,scope}=setup();let prompts=0;let requests=[]
|
||||
value.selected.packageCount=9;value.selected.cartons[0].countedAt='2026-10-16';value.pallets[0].bound=true
|
||||
scope.uni.showModal=()=>{prompts++}
|
||||
scope.palletizeWarehouseTransfer=async(no,payload)=>{requests.push(payload);throw new Error('offline')}
|
||||
await value.submit();assert.equal(requests.length,0);assert.equal(prompts,0)
|
||||
value.photoFileIds=[91];await value.submit();assert.equal(prompts,0);assert.equal(requests.length,1);assert.equal(requests[0].discrepancyReason,null)
|
||||
})
|
||||
|
||||
// 逐箱确认收起、再次编辑及草稿恢复必须保留其他箱的确认状态。
|
||||
test('individual confirmation collapses only that box and resumes from draft',async()=>{
|
||||
const {value,scope}=setup();value.addExtraCarton()
|
||||
Object.assign(value.cartonBoxes[1],{length:10,width:20,height:30,weight:2});value.cartonBoxes[1].items[0].quantity=5
|
||||
let saves=0;scope.countWarehouseTransferCartons=async()=>{saves++;throw new Error('offline')}
|
||||
await value.confirmSingleCarton(value.cartonBoxes[0])
|
||||
assert.equal(value.cartonConfirmed(value.cartonBoxes[0]),true);assert.equal(value.allCartonsConfirmed,false);assert.equal(saves,0)
|
||||
value.restoreCartons();assert.equal(value.cartonConfirmed(value.cartonBoxes[0]),true)
|
||||
await value.editCarton(value.cartonBoxes[0]);assert.equal(value.cartonConfirmed(value.cartonBoxes[0]),false)
|
||||
await value.confirmSingleCarton(value.cartonBoxes[0]);await value.confirmSingleCarton(value.cartonBoxes[1])
|
||||
assert.equal(value.allCartonsConfirmed,true);assert.equal(saves,1);assert.equal(value.error.message,'offline')
|
||||
value.cartonBoxes[0].weight=3;assert.equal(value.cartonConfirmed(value.cartonBoxes[0]),false);assert.equal(value.allCartonsConfirmed,false)
|
||||
})
|
||||
test('saved boxes restore collapsed and editing reopens only selected box',async()=>{
|
||||
const {value,scope}=setup()
|
||||
const first={...value.cartonBoxes[0],quantity:4,countedAt:'2026-10-16',status:'COUNTED'}
|
||||
const second={...first,cartonMark:'BOX2'}
|
||||
value.selected.cartons=[first,second];value.restoreCartons()
|
||||
assert.equal(value.cartonBoxes.every(box=>value.cartonConfirmed(box)),true)
|
||||
scope.reopenWarehouseTransferMeasurement=async()=>({...value.selected,cartons:value.selected.cartons.map(box=>({...box,countedAt:null}))})
|
||||
await value.editCarton(value.cartonBoxes[0])
|
||||
assert.equal(value.cartonsCounted,false);assert.equal(value.cartonConfirmed(value.cartonBoxes[0]),false);assert.equal(value.cartonConfirmed(value.cartonBoxes[1]),true)
|
||||
value.restoreCartons();assert.equal(value.cartonConfirmed(value.cartonBoxes[1]),true)
|
||||
})
|
||||
test('failed reopening preserves confirmed data and invalid box cannot confirm',async()=>{
|
||||
const {value,scope}=setup();value.cartonBoxes[0].weight=0
|
||||
await value.confirmSingleCarton(value.cartonBoxes[0]);assert.equal(value.cartonConfirmed(value.cartonBoxes[0]),false)
|
||||
value.cartonBoxes[0].weight=2;value.selected.cartons=[{...value.cartonBoxes[0],countedAt:'2026-10-16',status:'COUNTED'}];value.restoreCartons()
|
||||
scope.reopenWarehouseTransferMeasurement=async()=>{throw new Error('offline')}
|
||||
await value.editCarton(value.cartonBoxes[0]);assert.equal(value.cartonsCounted,true);assert.equal(value.cartonConfirmed(value.cartonBoxes[0]),true)
|
||||
})
|
||||
|
||||
// 箱数变化不影响 SKU 门禁,异 SKU 正负差异不能按总件数抵消。
|
||||
test('SKU differences compare each SKU and ignore carton count',()=>{
|
||||
const {value}=setup();value.selected.packageCount=9
|
||||
assert.equal(value.hasSkuReceiptDifference,false)
|
||||
value.selected.items.push({id:'I2',skuCode:'B',quantity:3})
|
||||
value.cartonBoxes[0].items=[{skuCode:'A',quantity:3},{skuCode:'B',quantity:4}]
|
||||
assert.equal(value.cartonQuantity,value.forecastSkuQuantity)
|
||||
assert.equal(value.skuReceiptDifferences.length,2)
|
||||
value.cartonBoxes[0].items=[{skuCode:'A',quantity:4},{skuCode:'B',quantity:3}]
|
||||
assert.equal(value.hasSkuReceiptDifference,false)
|
||||
})
|
||||
|
||||
// 客户补箱后,新增预报箱进入PDA,原本机草稿和删除标记不丢失。
|
||||
test('forecast additions merge into existing draft without losing edited boxes',()=>{
|
||||
const {value}=setup();value.cartonBoxes[0].weight=7;value.saveCartonDraft()
|
||||
value.selected.packageCount=2;value.selected.cartons.push({cartonMark:'BOX2',transferItemId:'I1',status:'EXPECTED'})
|
||||
value.restoreCartons();assert.equal(value.cartonBoxes.length,2);assert.equal(value.cartonBoxes[0].weight,7);assert.equal(value.cartonBoxes[1].cartonMark,'BOX2')
|
||||
value.removeCarton(0);value.restoreCartons();assert.equal(value.cartonBoxes.length,1);assert.equal(value.cartonBoxes[0].cartonMark,'BOX2')
|
||||
})
|
||||
test('count payload carries forecast version for stale snapshot rejection',async()=>{
|
||||
const {value,scope}=setup();let payload
|
||||
scope.countWarehouseTransferCartons=async(no,request)=>{payload=request;throw new Error('offline')}
|
||||
await value.confirmCartons();assert.equal(payload.forecastPackageCount,1);assert.equal(payload.boxes[0].cartonMark,'BOX1')
|
||||
})
|
||||
|
||||
@@ -58,14 +58,14 @@ test('delete only changes actual draft and preserves forecast; additions keep th
|
||||
test('draft reload preserves deletions, including an empty actual list', () => {
|
||||
const ctx = context()
|
||||
ctx.selected.cartons = [{ cartonMark: 'BOX1', transferItemId: '1', status: 'EXPECTED' }, { cartonMark: 'BOX2', transferItemId: '2', status: 'EXPECTED' }]
|
||||
let saved = { boxes: [{ ...box('B'), cartonMark: 'BOX2', transferItemId: '2' }], discrepancyReason: '少到一箱' }
|
||||
let saved = { forecastMarks: ['BOX1','BOX2'], boxes: [{ ...box('B'), cartonMark: 'BOX2', transferItemId: '2' }], discrepancyReason: '少到一箱' }
|
||||
globalThis.uni = { getStorageSync: () => saved }
|
||||
try {
|
||||
ctx.restoreCartons()
|
||||
assert.equal(ctx.cartonBoxes.length, 1)
|
||||
assert.equal(ctx.cartonBoxes[0].cartonMark, 'BOX2')
|
||||
assert.equal(ctx.countDiscrepancyReason, '少到一箱')
|
||||
saved = { boxes: [], discrepancyReason: '重新录入' }
|
||||
saved = { forecastMarks: ['BOX1','BOX2'], boxes: [], discrepancyReason: '重新录入' }
|
||||
ctx.restoreCartons()
|
||||
assert.equal(ctx.cartonBoxes.length, 0)
|
||||
assert.equal(ctx.selected.cartons.length, 2)
|
||||
@@ -93,7 +93,7 @@ test('100 same SKU cartons accept two distinct specification groups and independ
|
||||
assert.equal(ctx.palletSummary(ctx.pallets[1]).cartonCount, 40)
|
||||
assert.equal(ctx.cartonQuantity, 1040)
|
||||
assert.equal(ctx.totalWeight, '580.00')
|
||||
assert.equal(ctx.totalVolume, '3.840000')
|
||||
assert.equal(ctx.totalVolume, '3.840')
|
||||
assert.equal(ctx.cartonCountReady, true)
|
||||
ctx.cartonBoxes[0].items[0].quantity = 11
|
||||
ctx.cartonBoxes[0].weight = 6
|
||||
|
||||
Reference in New Issue
Block a user