diff --git a/AGENTS.md b/AGENTS.md
index 163d232..97e5561 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -40,7 +40,7 @@
| 登录、刷新与会话 | `src/pages/login/login.vue`、`src/api/auth.js`、`src/stores/session.js` | 国内 Auth 签发 Token;API 根见 `src/config/runtime.js` |
| 海外仓工作台与模块入口 | `src/pages/home/home.vue`、`src/pages/module/module.vue` | `src/data/overseas-workbench.js` 是模块与作业可用性入口;首页通过 `/api/pda/v1/last-leg/receiving-containers/warehouse-profile` 读取 Token 绑定仓的中文主档名称 |
| 中英西语言设置 | `src/i18n/index.js`、`src/i18n/locales/`、`src/pages/profile/profile.vue` | 个人中心可选择并持久化 `zh-CN`、`en`、`es`;当前业务翻译资源仍为空,页面业务文案继续使用中文,不得把语言选择入口误报为全量翻译完成 |
-| 柜号签收、照片与入仓真实作业 | `src/pages/operation/operation.vue`、`src/api/inbound.js`、`src/api/receiving-adapter.js`、`src/api/overseas.js`、`src/stores/receiving.js` | 三个固定照片角色各一张,支持拍照/相册、鉴权预览及更换失败保留原图;Backend `lastleg-service/inboundplan/pda/controller/ReceivingContainerController.java`、`ReceivingContainerService.java`,公网前缀 `/api/pda/v1/last-leg/receiving-containers/**` |
+| 柜号签收、照片与入仓真实作业 | `src/pages/operation/operation.vue`、`src/components/ReceivingSessionSummary.vue`、`src/api/inbound.js`、`src/api/receiving-adapter.js`、`src/api/overseas.js`、`src/stores/receiving.js` | 三个固定照片角色各一张,支持拍照/相册、鉴权预览及更换失败保留原图;多计划同柜直接展示服务端 `assigned/mergeAllowed/urgent/mergeHint`,合托明确为现场可选而非完成门禁;扫码实收确认计划生成阶段已预留的真实托盘和库位,不在设备端重新分配。Backend `lastleg-service/inboundplan/pda/controller/ReceivingContainerController.java`、`ReceivingContainerService.java`,公网前缀 `/api/pda/v1/last-leg/receiving-containers/**` |
| 托盘管理真实作业 | `src/pages/pallet/pallet.vue`、`src/api/pallet.js` | 扫码查询托盘内容,按服务端返回的托盘/库存版本执行组托、拆托、换托和解绑;公网前缀 `/api/pda/v1/last-leg/pallet-operations/**` |
| 库存盘点真实作业 | `src/pages/stocktake/stocktake.vue`、`src/api/warehouse-operations.js`、`src/stores/warehouse-work.js` | 创建/恢复盘点任务,扫描库位和库存后计数、提交;严格使用服务端版本和幂等键 |
| 商品移库真实作业 | `src/pages/move/move.vue`、`src/api/warehouse-operations.js`、`src/stores/warehouse-work.js` | 扫描来源托盘与目标库位执行同仓移库;冲突后重新读取服务端事实 |
diff --git a/src/api/receiving-adapter.js b/src/api/receiving-adapter.js
index b568bb2..9b73425 100644
--- a/src/api/receiving-adapter.js
+++ b/src/api/receiving-adapter.js
@@ -42,7 +42,11 @@ export function normalizeReceivingInbound(value = {}) {
plannedPalletCount: number(value.plannedPalletCount),
totalPackages: number(value.totalPackages),
receivedPackages: number(value.receivedPackages),
- pendingPackages: number(value.pendingPackages)
+ pendingPackages: number(value.pendingPackages),
+ assigned: value.assigned === true,
+ mergeAllowed: value.mergeAllowed === true,
+ urgent: value.urgent === true,
+ mergeHint: text(value.mergeHint)
}
}
diff --git a/src/components/ReceivingSessionSummary.vue b/src/components/ReceivingSessionSummary.vue
index 0a96e73..9e80c34 100644
--- a/src/components/ReceivingSessionSummary.vue
+++ b/src/components/ReceivingSessionSummary.vue
@@ -14,6 +14,16 @@
{{ totalPackages }}计划箱数
{{ pendingPackages }}待收箱数
+
+
+ {{ item.inboundCode || '—' }}
+
+ {{ item.assigned ? '已分配' : '未分配' }}
+ {{ item.mergeAllowed ? '可合托·非强制' : '不合托' }}
+ {{ item.urgent ? '加急' : '普通' }}
+
+
+
收货进度{{ receivedPackages }} / {{ totalPackages }} 箱
@@ -22,6 +32,7 @@
export default {
props: { session: { type: Object, default: null } },
computed: {
+ planFlags() { return Array.isArray(this.session?.inbounds) ? this.session.inbounds : [] },
receivingWarehouseLabel() { const code = String(this.session?.overseasWarehouseCode || '').trim(); const name = String(this.session?.overseasWarehouseName || '').trim(); return code && name ? `${code} · ${name}` : '—' },
inboundCodeCount() { return Array.isArray(this.session?.inbounds) ? this.session.inbounds.length : 0 },
plannedPalletCount() { return (this.session?.inbounds || []).reduce((sum, item) => sum + Math.max(0, Number(item?.plannedPalletCount || 0)), 0) },
@@ -42,6 +53,7 @@
.receiving-summary-status{flex-shrink:0;padding:4px 8px;border-radius:99px;color:#1668dc;font-size:9px;font-weight:700;background:#e3efff}
.receiving-summary-context{display:flex;flex-direction:column;gap:4px;margin-top:7px;color:#617389;font-size:9px;line-height:14px}.receiving-summary-warehouse{display:block}.receiving-summary-destinations{display:flex;min-width:0;align-items:flex-start}.destination-label{flex-shrink:0}.destination-tags{display:flex;min-width:0;flex-wrap:wrap;gap:3px 4px}.destination-tags text{padding:0 5px;border-radius:99px;color:#42617f;line-height:14px;background:#e8f0fa;white-space:nowrap}
.receiving-summary-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin-top:7px;padding:7px 2px;border-radius:9px;background:rgba(255,255,255,.8)}.receiving-summary-metrics>view{display:flex;align-items:center;flex-direction:column;border-right:1px solid #e3eaf2}.receiving-summary-metrics>view:last-child{border-right:0}
+ .receiving-plan-flags{margin-top:7px;padding:3px 8px;border-radius:9px;background:rgba(255,255,255,.82)}.receiving-plan-row{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:5px 0;border-bottom:1px solid #e9eef5}.receiving-plan-row:last-child{border-bottom:0}.receiving-plan-code{min-width:0;color:#17324d;font-size:10px;font-weight:750;overflow-wrap:anywhere}.receiving-plan-tags{display:flex;flex-shrink:0;gap:3px}.receiving-plan-tags text{padding:2px 5px;border-radius:99px;font-size:8px;line-height:12px}.flag-positive{color:#08705f;background:#e2f6f0}.flag-option{color:#1668dc;background:#e7f0ff}.flag-urgent{color:#b54708;background:#fff0dc}.flag-muted{color:#718095;background:#edf1f5}
.metric-value{color:#17324d;font-size:17px;font-weight:850;line-height:21px}.metric-pending{color:#b66a08}.metric-label{margin-top:1px;color:#718095;font-size:8px;line-height:12px;text-align:center}
.receiving-summary-progress{margin-top:7px;color:#607287;font-size:9px}.progress-track{height:5px;margin-top:5px;overflow:hidden;border-radius:99px;background:#dfe8f2}.progress-fill{height:100%;border-radius:inherit;background:#1677e8}
diff --git a/src/pages/operation/operation.vue b/src/pages/operation/operation.vue
index d9764e4..b7f305a 100644
--- a/src/pages/operation/operation.vue
+++ b/src/pages/operation/operation.vue
@@ -74,6 +74,8 @@
{{ activeInbound.destinationType === 'FBA' ? 'FBA 按箱数收货' : '海外仓逐箱收货' }}{{ destinationLabel(activeInbound) }}
{{ activeInbound.inboundCode }}
+ {{ activeInbound.assigned ? '已分配' : '未分配' }}{{ activeInbound.mergeAllowed ? '可合托(现场自选)' : '不合托' }}{{ activeInbound.urgent ? '加急' : '普通' }}
+ {{ activeInbound.mergeHint }}
总计 {{ activeInbound.totalPackages }} 箱已收 {{ activeInbound.receivedPackages }} 箱待收 {{ activeInbound.pendingPackages }} 箱
2. 扫描实物托盘码
@@ -199,6 +201,7 @@
.matched-inbound-card{margin:14px 0;padding:12px;border:1px solid #cfe0f2;border-radius:10px;background:#f8fbff}
.matched-inbound-card .mode-banner{margin-bottom:10px}
.matched-inbound-code{display:block;color:#17324d;font-size:15px;font-weight:800}
+ .matched-plan-flags{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}.matched-plan-flags text{padding:3px 7px;border-radius:99px;color:#617389;font-size:9px;background:#edf1f5}.matched-plan-flags .optional{color:#1668dc;background:#e7f0ff}.matched-plan-flags .urgent{color:#b54708;background:#fff0dc}.merge-option-hint{display:block;margin-top:7px;padding:7px 8px;border-radius:7px;color:#1668dc;font-size:10px;line-height:15px;background:#eef5ff}
.matched-inbound-progress{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin-top:8px;color:#5f7185;font-size:10px}
.matched-inbound-progress text{padding:6px 4px;border-radius:6px;text-align:center;background:#edf3f9}
.error-card text{overflow-wrap:anywhere;word-break:break-word}
diff --git a/tests/multi-plan-receiving.test.mjs b/tests/multi-plan-receiving.test.mjs
index 6fa82f5..313eb3d 100644
--- a/tests/multi-plan-receiving.test.mjs
+++ b/tests/multi-plan-receiving.test.mjs
@@ -15,7 +15,11 @@ const inbounds = Array.from({ length: 15 }, (_, index) => ({
plannedPalletCount: 1,
totalPackages: 4,
receivedPackages: index === 0 ? 4 : 0,
- pendingPackages: index === 0 ? 0 : 4
+ pendingPackages: index === 0 ? 0 : 4,
+ assigned: index % 3 === 0,
+ mergeAllowed: index % 4 === 0,
+ urgent: index === 0,
+ mergeHint: index % 4 === 0 ? '可选择合托,不强制' : ''
}))
test('one container candidate aggregates fifteen independent one-inbound plans', () => {
@@ -85,6 +89,9 @@ test('current recovery retains all independent-plan inbound facts while progress
assert.deepEqual(receivingProgress(), { total: 60, received: 4, pending: 56, percent: 7 })
assert.equal(receiving.current.inbounds.length, 15)
assert.equal(receiving.current.inbounds[0].inboundCode, 'NYEXX2608270001')
+ assert.equal(receiving.current.inbounds[0].assigned, true)
+ assert.equal(receiving.current.inbounds[0].mergeAllowed, true)
+ assert.equal(receiving.current.inbounds[0].urgent, true)
assert.equal(receiving.current.inbounds[14].inboundCode, 'NYEXX2608270015')
resetReceivingSession()
})
@@ -98,8 +105,10 @@ test('container UI locks and signs once across independent plans, then renders o
assert.match(operation, /item\.totalOrderCount[\s\S]*item\.inboundCodeCount[\s\S]*item\.planCount[\s\S]*item\.pendingPlanCount/)
assert.match(summary, /inboundCodeCount[\s\S]*plannedPalletCount[\s\S]*totalPackages/)
+ for (const fact of ['已分配', '未分配', '可合托·非强制', '不合托', '加急', '普通']) assert.match(summary, new RegExp(fact))
assert.doesNotMatch(operation, /v-for="[^"]*inbound[^"]*"/)
assert.match(operation, /v-if="activeInbound"[\s\S]*activeInbound\.inboundCode[\s\S]*activeInbound\.pendingPackages/)
+ assert.match(operation, /activeInbound\.mergeAllowed[\s\S]*现场自选[\s\S]*activeInbound\.urgent/)
assert.match(operation, /resolveCurrentCarton\(cartonMark\)/)
assert.doesNotMatch(operation, /cartonMark\.split|\.split\([^\n]*['"]#['"]/)