diff --git a/src/api/fulfillment.js b/src/api/fulfillment.js
index eb2eeba..a8b4f03 100644
--- a/src/api/fulfillment.js
+++ b/src/api/fulfillment.js
@@ -138,6 +138,7 @@ export function outboundPickPayload(item, position, packages, scannedCode = '')
outboundPalletItemId: item.outboundPalletItemId,
inventoryPositionId: position.inventoryPositionId || null,
packages: Number(packages),
+ ...(item.returnSkuQuantity != null ? { productQuantity: Number(item.returnSkuQuantity) } : {}),
cartonMarks: skuScan ? [] : position.cartonMark ? [position.cartonMark] : [],
...(skuScan ? { skuCode: item.skuCode } : {})
}
diff --git a/src/pages/outbound/outbound.vue b/src/pages/outbound/outbound.vue
index 7b120ea..1deda49 100644
--- a/src/pages/outbound/outbound.vue
+++ b/src/pages/outbound/outbound.vue
@@ -38,7 +38,7 @@
任务详情尚无可拣货明细请刷新;若持续为空,请联系平台确认出库货物是否已下发。
- 本次拣货:{{ lineBusinessCode(selectedPosition) }}拣货箱数最多 {{ selectedRemaining }} 箱
+ 本次拣货:{{ lineBusinessCode(selectedPosition) }}本来源计划出库 {{ selectedItem.returnSkuQuantity }} 件,请清点这些件数后确认;余数保留原箱。拣货箱数最多 {{ selectedRemaining }} 箱
完成出库任务尚有 {{ remainingPackages }} 箱未拣,不能完成出库。出库任务已完成,不可重复提交。全部计划货物已扫描核平,服务端将再次校验库存门禁。
diff --git a/src/pages/return-receipt/return-receipt.vue b/src/pages/return-receipt/return-receipt.vue
index 2ba4ebf..282835e 100644
--- a/src/pages/return-receipt/return-receipt.vue
+++ b/src/pages/return-receipt/return-receipt.vue
@@ -21,7 +21,7 @@
本次实物单元
- 包裹码/快递单号
+ 包裹码/快递单号(选填)
托盘码({{ unitType === 'BOX' ? '选填' : '必填' }}){{ palletCheck.empty ? '空托盘,可绑定当前退货单和 SKU' : `当前单托盘,已有 ${palletCheck.skuCodes.join('、')},共 ${palletCheck.quantity} 件` }}
@@ -30,7 +30,7 @@
{{ unitType === 'PARCEL' ? '本次实收数量' : (unitType === 'PALLET' ? '本托实收数量' : '逐箱实收数量') }}本托箱数合计{{ palletBoxCount }}本次箱数第 {{ index + 1 }} 箱{{ item.skuCode }} · 数量{{ item.skuCode }}{{ item.productNameZh || '商品名称未填写' }}剩余 {{ remaining(item) }}数量SKU 箱数
- 本托合计{{ receiptQuantity }}
+ {{ unitType === 'PARCEL' ? '本次合计' : '本托合计' }}{{ receiptQuantity }}
退货打托照片签收过程中可随时拍照{{ photoFileIds.length }} / 20 张{{ photoNames[index] || '退货打托照片' }}
本单已完成打托{{ pallet.palletCode }}{{ pallet.skuCode }} · {{ pallet.quantity }} 件签收批次:{{ pallet.receiptBatchNo }}
@@ -66,7 +66,7 @@
palletBoxCount() { return this.selectedItems.reduce((sum, item) => sum + this.boxCount(item.skuCode), 0) },
activeSkuCode() { return this.selectedItems.find(item => item.skuCode === this.selectedSku)?.skuCode || this.selectedItems[0]?.skuCode || '' },
receiptQuantity() { return this.unitType === 'BOX' ? this.boxRows.reduce((sum, box) => sum + Object.values(box.items).reduce((n, quantity) => n + (Number(quantity) || 0), 0), 0) : Object.values(warehouseWork.returnReceipt.quantities || {}).reduce((total, value) => total + (Number(value) || 0), 0) },
- canConfirm() { if (!this.current || !this.selectedItems.length || this.receiptQuantity <= 0) return false; if (this.unitType === 'BOX') return this.boxRows.length > 0 && this.boxRows.every(box => Object.values(box.items).some(quantity => Number(quantity) > 0)) && this.selectedItems.every(item => this.boxRows.reduce((sum, box) => sum + (Number(box.items[item.skuCode]) || 0), 0) <= this.remaining(item)); return (this.unitType === 'PARCEL' ? this.unitCode.trim() : this.palletCode.trim() && this.palletCheck && Number(this.palletLength) > 0 && Number(this.palletWidth) > 0 && Number(this.palletHeight) > 0 && Number(this.palletWeight) > 0 && (this.unitType !== 'PALLET' || this.palletBoxCount > 0)) && this.selectedItems.every((item) => this.quantity(item.skuCode) <= this.remaining(item) && (this.unitType !== 'PALLET' || this.quantity(item.skuCode) === 0 || this.boxCount(item.skuCode) > 0)); },
+ canConfirm() { if (!this.current || !this.selectedItems.length || this.receiptQuantity <= 0) return false; if (this.unitType === 'BOX') return this.boxRows.length > 0 && this.boxRows.every(box => Object.values(box.items).some(quantity => Number(quantity) > 0)) && this.selectedItems.every(item => this.boxRows.reduce((sum, box) => sum + (Number(box.items[item.skuCode]) || 0), 0) <= this.remaining(item)); return (this.unitType === 'PARCEL' || (this.palletCode.trim() && this.palletCheck && Number(this.palletLength) > 0 && Number(this.palletWidth) > 0 && Number(this.palletHeight) > 0 && Number(this.palletWeight) > 0 && (this.unitType !== 'PALLET' || this.palletBoxCount > 0))) && this.selectedItems.every((item) => this.quantity(item.skuCode) <= this.remaining(item) && (this.unitType !== 'PALLET' || this.quantity(item.skuCode) === 0 || this.boxCount(item.skuCode) > 0)); },
unitLabel() { return this.unitType === 'PALLET' ? '托盘码' : this.unitType === 'BOX' ? '箱号/箱唛' : '包裹码/快递单号' },
unitPlaceholder() { return this.unitType === 'PALLET' ? '扫描托盘标签' : this.unitType === 'BOX' ? '扫描箱唛或录入箱号' : '扫描包裹面单或录入包裹码' }
,palletVolume() { const l = Number(this.palletLength) || 0; const w = Number(this.palletWidth) || 0; const h = Number(this.palletHeight) || 0; return l > 0 && w > 0 && h > 0 ? (l * w * h).toFixed(2) : '—' }
diff --git a/tests/fulfillment-workflows.test.mjs b/tests/fulfillment-workflows.test.mjs
index 2c65cc7..a219a76 100644
--- a/tests/fulfillment-workflows.test.mjs
+++ b/tests/fulfillment-workflows.test.mjs
@@ -338,3 +338,11 @@ test('dispatch completed or ambiguous pallet scan never silently selects a row',
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);
+});
diff --git a/tests/return-receipt-page.test.mjs b/tests/return-receipt-page.test.mjs
index 2b0e954..224b108 100644
--- a/tests/return-receipt-page.test.mjs
+++ b/tests/return-receipt-page.test.mjs
@@ -57,6 +57,23 @@ test('PALLET page submits the required palletCode through the actual confirmatio
assert.equal(page.current, null)
})
+test('parcel signing allows an empty code but still rejects quantities above the remaining amount', async () => {
+ const source = readFileSync(new URL('../src/pages/return-receipt/return-receipt.vue', import.meta.url), 'utf8')
+ const component = vm.runInNewContext(source.match(/