From a646ade658ae51372e838c04775e51487e8ccd0d Mon Sep 17 00:00:00 2001
From: liuyihe <2513762445@qq.com>
Date: Mon, 28 Sep 2026 17:26:50 +0800
Subject: [PATCH 01/21] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8F=90=E4=BA=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/fulfillment.js | 32 ++++++++++++++
src/data/overseas-workbench.js | 2 +-
src/pages/outbound/outbound.vue | 33 ++++++++++----
.../warehouse-transfer/warehouse-transfer.vue | 43 +++++++++++++++----
tests/warehouse-transfer-api.test.mjs | 10 +++++
5 files changed, 102 insertions(+), 18 deletions(-)
diff --git a/src/api/fulfillment.js b/src/api/fulfillment.js
index 0a82511..729bb73 100644
--- a/src/api/fulfillment.js
+++ b/src/api/fulfillment.js
@@ -2,6 +2,7 @@ import { overseasRequest } from './overseas.js'
const DISPATCH_BASE = '/pda/v1/last-leg/dispatches'
const OUTBOUND_BASE = '/pda/v1/last-leg/outbound-tasks'
+const CARGO_DISCARD_BASE = '/pda/v1/last-leg/cargo-discards'
const encoded = (value) => encodeURIComponent(String(value || '').trim())
@@ -128,3 +129,34 @@ export function outboundPickPayload(item, position, packages) {
export function completeOutboundTask(taskNo, request) {
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/complete`, request)
}
+
+export function normalizeCargoDiscardTask(value) {
+ const task = value || {}
+ return {
+ ...task,
+ discardNo: String(task?.discardNo || ''),
+ palletNo: String(task?.palletNo || ''),
+ status: String(task?.status || 'PENDING'),
+ skus: asList(task?.skus).map((sku) => ({
+ ...sku,
+ itemId: String(sku?.itemId || ''),
+ skuCode: String(sku?.skuCode || ''),
+ productName: String(sku?.productName || ''),
+ plannedPackages: count(sku?.plannedPackages),
+ actualPackages: sku?.actualPackages == null ? null : count(sku.actualPackages),
+ status: String(sku?.status || 'PENDING')
+ }))
+ }
+}
+
+export async function listCargoDiscardTasks() {
+ return asList(await overseasRequest('GET', CARGO_DISCARD_BASE)).map(normalizeCargoDiscardTask)
+}
+
+export async function resolveCargoDiscardPallet(palletNo) {
+ return normalizeCargoDiscardTask(await overseasRequest('POST', `${CARGO_DISCARD_BASE}/resolve`, { palletNo }))
+}
+
+export async function confirmCargoDiscard(discardNo, palletNo, lines) {
+ return normalizeCargoDiscardTask(await overseasRequest('POST', `${CARGO_DISCARD_BASE}/${encoded(discardNo)}/pallets/${encoded(palletNo)}/confirm`, { lines }))
+}
diff --git a/src/data/overseas-workbench.js b/src/data/overseas-workbench.js
index 60b9292..d99c613 100644
--- a/src/data/overseas-workbench.js
+++ b/src/data/overseas-workbench.js
@@ -8,7 +8,7 @@ export const MODULES = [
] },
{ key: 'outbound', name: '出库履约', nameKey: 'modules.outbound.name', icon: '出', description: '排车拣货、出库任务与发车闭环', descriptionKey: 'modules.outbound.description', operations: [
{ key: 'dispatchPicking', name: '排车拣货', nameKey: 'modules.dispatchPicking.name', icon: '车', iconType: 'paperplane', description: '按排车任务扫描托盘拣货,完成门禁后执行发车', descriptionKey: 'modules.dispatchPicking.description', available: true, route: '/pages/dispatch/dispatch' },
- { key: 'outboundPicking', name: '出库作业', nameKey: 'modules.outboundPicking.name', icon: '拣', iconType: 'checkbox', description: '处理出库与一件代发任务,扫码核平后完成', descriptionKey: 'modules.outboundPicking.description', available: true, route: '/pages/outbound/outbound' }
+ { key: 'outboundPicking', name: '出库作业', nameKey: 'modules.outboundPicking.name', icon: '拣', iconType: 'checkbox', description: '处理普通出库、一件代发和废弃订单,按托盘扫码完成', descriptionKey: 'modules.outboundPicking.description', available: true, route: '/pages/outbound/outbound' }
] },
{ key: 'inventory', name: '库内作业', nameKey: 'modules.inventory.name', icon: '库', description: '商品移库与托盘管理', descriptionKey: 'modules.inventory.description', operations: [
{ key: 'move', name: '商品移库', nameKey: 'modules.move.name', icon: '移', iconType: 'loop', description: '扫描来源与目标库位完成移库', descriptionKey: 'modules.move.description', available: true, route: '/pages/move/move' },
diff --git a/src/pages/outbound/outbound.vue b/src/pages/outbound/outbound.vue
index b1dd1c8..63bf4a5 100644
--- a/src/pages/outbound/outbound.vue
+++ b/src/pages/outbound/outbound.vue
@@ -6,8 +6,8 @@
出库任务一件代发扫描托盘码可整托出库;扫描排车业务号会转入“排车拣货”
当前任务出库作业详情
-
-
+
+
任务号直接恢复;进入任务后扫描 DP 托盘码,将一次拣取该托全部未拣箱件。
@@ -19,7 +19,8 @@
- 暂无出库任务可刷新列表,或扫描任务号恢复未完成作业。
+ 暂无出库任务可刷新列表,或扫描任务号恢复未完成作业。
+ 废弃订单{{ discardOrderCount }} 个订单
@@ -41,6 +42,12 @@
完成出库任务尚有 {{ remainingPackages }} 箱未拣,不能完成出库。出库任务已完成,不可重复提交。全部计划货物已扫描核平,服务端将再次校验库存门禁。
+
+ 废弃订单{{ discardCurrent.discardNo }}
+ 扫描该废弃订单对应的托盘,再填写每个 SKU 的实际废弃数量。
+ 托盘 {{ discardTask.palletNo }}{{ discardTask.status === 'COMPLETED' ? '已完成' : '待废弃' }}{{ sku.skuCode || '—' }}{{ sku.productName || '—' }} · 计划 {{ sku.plannedPackages }} 箱实际废弃数量最多 {{ sku.plannedPackages }} 箱
+
+
{{ successMessage }}
@@ -50,7 +57,7 @@
diff --git a/src/pages/return-packing/return-packing.vue b/src/pages/return-packing/return-packing.vue
index 835c67b..c237bff 100644
--- a/src/pages/return-packing/return-packing.vue
+++ b/src/pages/return-packing/return-packing.vue
@@ -2,7 +2,12 @@
- 添加退货单
+
+ 待执行合箱计划
+ 无计划时自主合箱
+
+
+ {{ activePlan.planNo }}{{ activePlan.mode === 'CUSTOMER_INSTRUCTION' ? '客户合箱指令' : '仓库自主合箱' }} · 来源订单 {{ activePlan.returnNos.length }} 个计划未配置箱明细时,系统已按单 SKU 一箱生成默认方案。可以合并为混箱,也可以拆成多个单箱。第 {{ index + 1 }} 箱
没有匹配的已完成退货单或可用库存
@@ -13,7 +18,7 @@
- 退货合箱成功{{ result.label.cartonMark || result.cartonMark }}来源退货单:{{ result.label.sourceReturnNo || '—' }}箱规:{{ sizeText(result.label) }}{{ item.skuCode }}{{ item.quantity }} 件{{ item.productNameZh || '商品名称未填写' }}
+ 退货合箱成功,共 {{ (result.labels || [result.label]).length }} 箱{{ label.cartonMark }}来源退货单:{{ label.sourceReturnNo || '—' }}箱规:{{ sizeText(label) }}{{ item.skuCode }}{{ item.quantity }} 件
{{ error.message }}错误码:{{ error.code }}requestId:{{ error.requestId }}traceId:{{ error.traceId }}
@@ -22,7 +27,7 @@
+
diff --git a/src/pages/dispatch/dispatch.vue b/src/pages/dispatch/dispatch.vue
index 31dbb60..ba9b125 100644
--- a/src/pages/dispatch/dispatch.vue
+++ b/src/pages/dispatch/dispatch.vue
@@ -30,7 +30,7 @@
@@ -38,9 +38,10 @@
+
{{ current.dispatchNo || '—' }}{{ statusLabel(current.status) }}
- 任务号 {{ current.taskNo || '—' }} · 车牌 {{ current.vehiclePlate || '—' }}
+ 任务号 {{ current.taskNo || '—' }} · {{ departed ? '原发车车牌' : '车牌' }} {{ current.vehiclePlate || '—' }}
{{ current.pickBlockReason || '请先通过调度审核再拣货' }}
{{ plannedPackages }}计划箱数{{ pickedPackages }}已拣箱数{{ remainingPackages }}待拣箱数
@@ -88,6 +89,7 @@
diff --git a/tests/user-logout.test.mjs b/tests/user-logout.test.mjs
new file mode 100644
index 0000000..26c8430
--- /dev/null
+++ b/tests/user-logout.test.mjs
@@ -0,0 +1,116 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import { PDA_ROLE, PDA_AUTHORITY, session, signInWithTokens, signOut, getSessionRevision } from '../src/stores/session.js'
+import { receiving } from '../src/stores/receiving.js'
+import { refresh, getPendingRefresh } from '../src/api/auth.js'
+import { overseasRequest } from '../src/api/overseas.js'
+
+const helperUrl = new URL('../src/api/logout-session.js', import.meta.url)
+const logoutHelper = fs.existsSync(helperUrl) ? await import(helperUrl) : {}
+const jwt = claims => [Buffer.from('{"alg":"none"}').toString('base64url'), Buffer.from(JSON.stringify(claims)).toString('base64url'), 'signature'].join('.')
+function tokens(version='old', identity={}) { return {accessToken:jwt({user_id:42,membership_id:84,tenant_id:0,app_code:'PLATFORM',overseas_warehouse_id:900,exp:Math.floor(Date.now()/1000)+3600,version,...identity}),refreshToken:`refresh-${version}`,roles:[PDA_ROLE],authorities:[PDA_AUTHORITY]} }
+const ok = data => ({statusCode:200,data:{code:'200',data},header:{}})
+function prepare(){const values=new Map(),calls=[],launches=[],toasts=[];globalThis.uni={setStorageSync:(k,v)=>values.set(k,JSON.parse(JSON.stringify(v))),getStorageSync:k=>values.get(k),removeStorageSync:k=>values.delete(k),reLaunch:o=>launches.push(o.url),showToast:o=>toasts.push(o.title)};signOut();const original=tokens();signInWithTokens(original);receiving.current={containerNo:'ORIGINAL'};return {values,calls,launches,toasts,original}}
+function page(){const source=fs.readFileSync(new URL('../src/pages/profile/profile.vue',import.meta.url),'utf8').match(/