From 648b0c858da21ee87582736446b50cb784517b09 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Fri, 9 Oct 2026 11:49:03 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/return-packing/return-packing.vue | 6 +-- src/pages/return-receipt/return-receipt.vue | 43 +++++++++++---- tests/return-receipt-page.test.mjs | 58 +++++++++++++++++++++ 3 files changed, 93 insertions(+), 14 deletions(-) diff --git a/src/pages/return-packing/return-packing.vue b/src/pages/return-packing/return-packing.vue index 9c03496..5009c5f 100644 --- a/src/pages/return-packing/return-packing.vue +++ b/src/pages/return-packing/return-packing.vue @@ -4,14 +4,14 @@ - 扫描 SKU 或箱号 - 待作业合箱计划审核通过后推送 + 仓库现场直接合箱扫描 SKU 或箱号开始;系统会自动锁定客户和当前仓,跨客户货物不能合箱。 + 平台合箱计划审核通过后推送按计划锁定的来源箱和数量作业,后续合箱规则不变。 选择客户该 SKU 在多个客户有可用退货库存,请先选择客户 没有匹配的已完成退货单或可用库存 - 退货签收成功第 {{ index + 1 }} 箱 · {{ label.cartonMark }}{{ result.unitType === 'PALLET' ? '托盘标签' : '箱唛' }} · {{ result.cartonMark }}托盘:{{ result.palletCode }}签收单:{{ result.receiptNo }}计费批次:{{ result.receiptBatchNo }}{{ result.receivedQuantity }}/{{ result.expectedQuantity }}整单累计{{ result.fullyMatched ? '已完成' : '签收中' }}退货单状态 + 退货签收成功第 {{ index + 1 }} 箱 · {{ label.cartonMark }}{{ result.unitType === 'PALLET' ? '托盘标签' : '箱唛' }} · {{ result.cartonMark }}托盘:{{ result.palletCode }}签收单:{{ result.receiptNo }}计费批次:{{ result.receiptBatchNo }}{{ result.receivedQuantity }}/{{ result.expectedQuantity }}整单累计{{ result.fullyMatched ? '已完成' : '签收中' }}退货单状态 {{ error.message }}错误码:{{ error.code }}requestId:{{ error.requestId }}traceId:{{ error.traceId }} @@ -74,6 +74,7 @@ onShow() { if (!requireSession()) return; this.user = session.currentUser; restoreWarehouseWork(`${this.user.id}:${this.user.warehouseId}`); this.entryMode = 'SKU'; this.startScanner(); if (!this.receiptBatch && warehouseWork.returnReceipt.currentReturnNo && !this.current && !this.busy) this.openOrder(warehouseWork.returnReceipt.currentReturnNo, 'SKU', false) }, onHide() { this.stopScanner() }, onUnload() { this.stopScanner() }, beforeUnmount() { this.stopScanner() }, methods: { + goCount() { const mark = this.result?.labels?.[0]?.cartonMark; if (mark) uni.navigateTo({ url: '/pages/return-counting/return-counting?cartonMark=' + encodeURIComponent(mark) }) }, selectUnitType(type) { if (this.busy) return; this.unitType = type; this.unitCode = ''; this.boxRows = [{ items: {} }]; this.palletCheck = null; this.activeTarget = type === 'PALLET' ? 'pallet' : 'unit' }, resizeBoxRows(raw) { const count = Math.floor(Number(raw) || 0); if (count < 1 || count > 100) return; this.boxRows = Array.from({ length: count }, (_, index) => this.boxRows[index] || { items: {} }) }, setBoxQuantity(index, skuCode, raw) { const value = Math.max(0, Math.floor(Number(raw) || 0)); this.boxRows[index].items = { ...this.boxRows[index].items, [skuCode]: value } }, diff --git a/tests/return-counting-api.test.mjs b/tests/return-counting-api.test.mjs new file mode 100644 index 0000000..a3946c0 --- /dev/null +++ b/tests/return-counting-api.test.mjs @@ -0,0 +1,43 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { listReturnCountTasks, scanReturnCountCarton, scanReturnCountTasks, getCurrentReturnCountTask, beginReturnCountTask, confirmReturnCount } from '../src/api/return-counting.js' +import { session } from '../src/stores/session.js' + +test('counting preserves box contents and retry key without accepting client warehouse scope', async () => { + session.apiBase = '/api'; session.token = 'counting-test' + const calls = [] + globalThis.uni = { request(options) { calls.push(options); options.success({ statusCode: 200, data: { code: '200', data: { status: 'PENDING' } }, header: {} }) } } + await listReturnCountTasks({ page: 2, keyword: ' RC#2-1 ' }) + await scanReturnCountCarton('RC#2-1') + const request = { idempotencyKey: 'count-retry', boxes: [{ cartonMark: 'RC#2-1', items: [{ skuCode: 'SKU-1', quantity: 3 }] }, { cartonMark: 'RC#2-2', items: [{ skuCode: 'SKU-1', quantity: 7 }] }] } + await confirmReturnCount('RR/1', request) + assert.deepEqual(calls.map(row => [row.method, row.url]), [ + ['GET', '/api/pda/v1/last-leg/return-inventory/counting/tasks?page=2&size=10&keyword=RC%232-1'], + ['GET', '/api/pda/v1/last-leg/return-inventory/counting/cartons/RC%232-1'], + ['POST', '/api/pda/v1/last-leg/return-inventory/counting/receipts/RR%2F1'] + ]) + assert.deepEqual(calls[2].data, request) + assert.ok(calls.every(row => row.header.Authorization === 'Bearer counting-test')) +}) + +test('unified scan supports box and pallet codes and preserves the no-count empty result', async () => { + session.apiBase = '/api'; session.token = 'counting-test' + const urls = [] + globalThis.uni = { request(options) { urls.push(options.url); options.success({ statusCode: 200, data: { code: '200', data: [] }, header: {} }) } } + assert.deepEqual(await scanReturnCountTasks(' RC#2-1 '), []) + assert.deepEqual(await scanReturnCountTasks('PLT/1'), []) + assert.deepEqual(urls, ['/api/pda/v1/last-leg/return-inventory/counting/scan/RC%232-1', '/api/pda/v1/last-leg/return-inventory/counting/scan/PLT%2F1']) +}) + +test('current task preserves null and begin uses the operator-scoped claim route', async () => { + session.apiBase = '/api'; session.token = 'counting-test' + const calls = [] + globalThis.uni = { request(options) { calls.push(options); options.success({ statusCode: 200, data: { code: '200', data: options.method === 'GET' ? null : { receiptNo: 'RR/1', status: 'COUNTING' } }, header: {} }) } } + assert.equal(await getCurrentReturnCountTask(), null) + assert.equal((await beginReturnCountTask('RR/1')).status, 'COUNTING') + assert.deepEqual(calls.map(row => [row.method, row.url]), [ + ['GET', '/api/pda/v1/last-leg/return-inventory/counting/current'], + ['POST', '/api/pda/v1/last-leg/return-inventory/counting/receipts/RR%2F1/begin'] + ]) + assert.equal(calls[1].data, undefined) +}) diff --git a/tests/return-counting-page.test.mjs b/tests/return-counting-page.test.mjs new file mode 100644 index 0000000..f00a954 --- /dev/null +++ b/tests/return-counting-page.test.mjs @@ -0,0 +1,157 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { runInNewContext } from 'node:vm' + +const source = readFileSync(new URL('../src/pages/return-counting/return-counting.vue', import.meta.url), 'utf8') +const script = source.match(/ - +.destination-summary{display:flex;align-items:center;flex-wrap:wrap;gap:5px;margin-top:6px}.destination-summary .meta-line{margin:0}.destination-tag{display:inline-block;padding:3px 7px;border-radius:5px;background:#edf4ff;color:#285d9f;font-size:10px;line-height:14px} +.carton-label-list{margin-top:10px;gap:6px}.carton-label-item{display:flex;align-items:center;flex-wrap:nowrap;gap:6px;padding:7px 0;min-width:0}.carton-label{display:flex;flex:1;flex-direction:column;gap:2px;box-sizing:border-box;min-width:0;overflow:hidden;padding:5px 7px;border:1px solid #dce5ef;border-radius:6px;background:#f6f8fb}.fba-label{border-color:#cddff9;background:#eff5ff}.label-caption{color:#718095;font-size:9px;line-height:13px}.label-code{display:block;color:#253e5b;font-size:10px;font-weight:600;line-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.label-arrow{flex-shrink:0;color:#8090a3;font-size:12px}.carton-label-item>.destination-tag{flex-shrink:0}.sku-detail-card{padding:8px 10px}.sku-detail-card .business-code{font-size:12px;font-weight:600;line-height:18px;overflow-wrap:anywhere}.sku-detail-card .meta-line{font-size:9px;line-height:14px}.sku-detail-card .link-button{height:26px;font-size:9px;line-height:26px} +.summary-metrics{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:4px;margin-left:auto;font-size:9px;line-height:18px}.summary-metric{display:inline-block;padding:1px 4px;border-radius:4px;background:#dcfce7;color:#16a34a;font-weight:700;white-space:nowrap} +.candidate-heading{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 8px}.candidate-customer{color:#52647c;font-size:10px;line-height:16px;overflow-wrap:anywhere} diff --git a/src/pages/return-receipt/return-receipt.vue b/src/pages/return-receipt/return-receipt.vue index 282835e..5ec3f47 100644 --- a/src/pages/return-receipt/return-receipt.vue +++ b/src/pages/return-receipt/return-receipt.vue @@ -21,7 +21,7 @@ 本次实物单元 - 包裹码/快递单号(选填) + 快递单号(选填)