From 343a5ea260bf04c54bbbac89e3479c8d35b5c70d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 15 Sep 2026 21:05:09 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E6=B5=B7=E5=A4=96=E5=87=BA?= =?UTF-8?q?=E5=BA=93=E7=8A=B6=E6=80=81=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/overseas-warehouse/AGENTS.md | 2 +- .../overseas-warehouse/pages/OutboundDetailView.test.ts | 5 +++-- src/modules/overseas-warehouse/pages/OutboundsView.test.ts | 4 ++-- src/shared/components/StatusBadge.vue | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/modules/overseas-warehouse/AGENTS.md b/src/modules/overseas-warehouse/AGENTS.md index 9a2be712..e184b88f 100644 --- a/src/modules/overseas-warehouse/AGENTS.md +++ b/src/modules/overseas-warehouse/AGENTS.md @@ -22,7 +22,7 @@ | 尾程订单控制与柜级入仓计划转换 | `pages/LastMileOrdersView.vue`、`pages/LastMileOrderDetailView.vue` | 柜子工作台按 `sourceContainerBookingId` 消费 `/orders/container-summaries`;服务端分页返回全部稳定来源柜,计划已经齐全的柜仍保留用于接柜仓查询和修改,并按冻结 `plannedArrivalAt` 升序、空值最后排序。详情返回全柜订单且不受列表分页截断;单票手工计划保存后,由服务端在整柜最后一票计划齐全时自动生成并绑定全柜托盘码,一票柜立即生成;`resourcePendingPlanCount` 只表示自动生成异常的重试入口,不是正常人工步骤。柜级确认弹框先调用 `/inbound-plans/container-preview`,按服务端 FBA/一件代发/私人地址分组展示头程已有、海外仓补充和最终托数以及 `readiness/blockers`;散货最终托数不得少于头程已有托盘码,差额连续补码且不阻断流程;一件代发必须严格按不可变头程封托托盘数整体入库,单票表单托数锁定且服务端再次失败关闭校验。前端不计算托数、不逐票建计划、不预占托盘,再把同一 `snapshotVersion/snapshotHash` 原样提交 `/inbound-plans/convert-container`,快照冲突后重新获取预览。服务端只为剩余订单逐票生成独立计划,并为异常遗留的人工计划补齐真实托盘,整批原子且重放不得产生第二批资源。单票完整入库计划、暂扣/解除和八行人工节点均从 `api/lastMileOrders.ts` 提交订单版本与幂等键;预计到仓时间只读展示来源柜冻结值,不进入人工计划请求,服务端按整柜校验并写入同一日期;入仓码只读展示并提交订单冻结值,不允许人工改写,暂扣资格、接柜仓锁定原因和操作能力只消费服务端结果,人工节点一经保存不得回退柜级节点。转仓候选只取 `../iam/api/platformIam.ts` 的平台公共 `ACTIVE + FBA` 仓主档;Backend `lastleg-service/` | | 入仓单、入仓计划、FBA 预约 | `pages/InboundOrdersView.vue`、`pages/InboundOrderDetailView.vue` | 列表/预约 `api/inboundOrders.ts`,计划列表/详情 `api/inboundPlans.ts`;Backend `lastleg-service/inboundplan/` | | 排车查询、智能预排与转派送 | `pages/DispatchesView.vue`、`pages/DispatchDetailView.vue`、`components/SmartDispatchDialog.vue` | `api/dispatches.ts`;Backend `lastleg-service/dispatch/`、`delivery/`;覆盖详情、候选、草稿柜、托盘锁、`pre-arrange` 和 `transfer-delivery`。路线只读并由已排托盘关联的 FBA 仓去重后以英文逗号连接,预排和普通编辑均不接收人工路线。一次转派送仍生成一张车辆级出库单,响应的 `deliveries` 按目的 FBA 仓返回多张派送单。页面能力必须由正式路由、API 调用和测试共同证明 | -| 海外出库与派送 | `pages/OutboundsView.vue`、`pages/OutboundDetailView.vue`、`pages/DeliveryOrdersView.vue`、`pages/DeliveryOrderDetailView.vue` | `api/outbounds.ts`、`api/deliveryOrders.ts`;出库详情使用 `deliveries` 展示按目的 FBA 仓拆出的全部派送单,派送列表和详情必须显示目的 FBA 仓。人工状态入口不提供完成派送;签收资料保存成功后提示自动完成并刷新列表。出库列表按类型筛选并展示中文类型。一件代发新建弹窗直接跨客户加载服务端可用候选,不单独展示客户筛选,海外仓必选并优先默认洛杉矶仓;订单条件可按订单号、入库单号、客户、托盘号、SKU 或品名查询/重置,以托盘+SKU 行分页多选。提交时从所选行解析唯一客户,并校验所选行属于当前唯一海外仓,不要求先选客户。退货出库客户下拉同时展示客户名称与联系电话并支持手机号检索,客户和仓库选项独立容错加载;选择客户后须立即按当前默认/已选海外仓加载库存。SKU 与箱号查询均使用输入态/已查询态分离,只允许点击查询按钮后生效;支持英文逗号批量条件,查询时清除空格并按任一条件命中,左右列表分别提供每页 5/10/20/50 条选择。SKU 列表只展示 `availableQuantity > 0` 的未占用可用数量,合箱后已扣减为零的 SKU 不再作为可选库存显示,实际作业以可用箱唛选择和箱内 SKU 明细扣减为准。取消、派送列表/详情及退回入口对应 Backend `lastleg-service/delivery/`、`dropship/` | +| 海外出库与派送 | `pages/OutboundsView.vue`、`pages/OutboundDetailView.vue`、`pages/DeliveryOrdersView.vue`、`pages/DeliveryOrderDetailView.vue` | `api/outbounds.ts`、`api/deliveryOrders.ts`;出库详情使用 `deliveries` 展示按目的 FBA 仓拆出的全部派送单,派送列表和详情必须显示目的 FBA 仓。人工状态入口不提供完成派送;签收资料保存成功后提示自动完成并刷新列表。出库列表按类型筛选并展示中文类型,状态须覆盖 `WAIT_PICK/待拣货`、`PICKING/拣货中`、`PICKED/已拣货`、`WAIT_OUTBOUND/待出库`、`OUTBOUNDED/已出库` 和 `CANCELLED/已取消`;仅未开始的 `WAIT_PICK/WAIT_OUTBOUND` 允许取消。一件代发新建弹窗直接跨客户加载服务端可用候选,不单独展示客户筛选,海外仓必选并优先默认洛杉矶仓;订单条件可按订单号、入库单号、客户、托盘号、SKU 或品名查询/重置,以托盘+SKU 行分页多选。提交时从所选行解析唯一客户,并校验所选行属于当前唯一海外仓,不要求先选客户。退货出库客户下拉同时展示客户名称与联系电话并支持手机号检索,客户和仓库选项独立容错加载;选择客户后须立即按当前默认/已选海外仓加载库存。SKU 与箱号查询均使用输入态/已查询态分离,只允许点击查询按钮后生效;支持英文逗号批量条件,查询时清除空格并按任一条件命中,左右列表分别提供每页 5/10/20/50 条选择。SKU 列表只展示 `availableQuantity > 0` 的未占用可用数量,合箱后已扣减为零的 SKU 不再作为可选库存显示,实际作业以可用箱唛选择和箱内 SKU 明细扣减为准。取消、派送列表/详情及退回入口对应 Backend `lastleg-service/delivery/`、`dropship/` | | 异常退回与客户回访 | `pages/ExceptionOrdersView.vue`、`pages/CustomerVisitsView.vue` | `api/exceptionOrders.ts`、`api/customerVisits.ts`;异常关系解析、异常建档、派送退回、完成/状态和回访附件对应 Backend `lastleg-service/exceptionorder/`、`customervisit/` | | 库存余额、库位与流水 | `pages/InventoryView.vue`、`pages/InventoryDetailView.vue`、`pages/InventoryTransactionsView.vue` | `api/inventory.ts`;业务库存归 `lastleg-service/inventory/`,实物资源归 `warehouse-overseas-service/` | | 托盘、箱唛、产品与换标 | `pages/PalletsView.vue`、`pages/CartonsView.vue`、`pages/ProductsView.vue`、`pages/RelabelOrdersView.vue` 及详情页 | `api/pallets.ts`、`api/relabelOrders.ts`;换标详情展示 PDA 完工照片与复核人,状态区分待客户确认和待出库;路由是否公开以 `routes.ts` 为准 | diff --git a/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts b/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts index 7b61c2e7..7ad5c149 100644 --- a/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts +++ b/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts @@ -12,7 +12,7 @@ vi.mock('../api/outbounds', () => ({ getOutbound: vi.fn(), cancelOutbound: vi.fn describe('OutboundDetailView', () => { it.each([ - ['WAIT_OUTBOUND', '待出库', 'warning'], ['PICKING', '拣货中', 'info'], + ['WAIT_PICK', '待拣货', 'warning'], ['WAIT_OUTBOUND', '待出库', 'warning'], ['PICKING', '拣货中', 'info'], ['PICKED', '已拣货', 'warning'], ['OUTBOUNDED', '已出库', 'success'], ['CANCELLED', '已取消', 'danger'], ])('uses the same shared status tone as the list for %s', async (status, label, tone) => { vi.mocked(getOutbound).mockResolvedValueOnce({ outbound: { id: '1', outboundNo: 'OUT/1', status }, pallets: [], deliveries: [] }) @@ -78,8 +78,9 @@ describe('OutboundDetailView', () => { emptyWrapper.unmount() }) - it('shows cancellation only for PICKED with the independent permission', async () => { + it('shows cancellation only before picking with the independent permission', async () => { canPermission.mockImplementation((code: string) => code === 'platform:overseas-warehouse:outbound:cancel') + vi.mocked(getOutbound).mockResolvedValueOnce({ outbound: { id: '1', outboundNo: 'OUT/1', status: 'WAIT_PICK' }, pallets: [], deliveries: [] }) const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.text()).toContain('取消出库单') diff --git a/src/modules/overseas-warehouse/pages/OutboundsView.test.ts b/src/modules/overseas-warehouse/pages/OutboundsView.test.ts index 8515bd2c..30e9700d 100644 --- a/src/modules/overseas-warehouse/pages/OutboundsView.test.ts +++ b/src/modules/overseas-warehouse/pages/OutboundsView.test.ts @@ -19,7 +19,7 @@ class ResizeObserverStub { observe() {} unobserve() {} disconnect() {} } describe('OutboundsView permissions and navigation', () => { it('distinguishes outbound stages and does not present unknown states as picking', async () => { const statuses = [ - ['WAIT_OUTBOUND', '待出库', 'warning'], ['PICKING', '拣货中', 'info'], + ['WAIT_PICK', '待拣货', 'warning'], ['WAIT_OUTBOUND', '待出库', 'warning'], ['PICKING', '拣货中', 'info'], ['PICKED', '已拣货', 'warning'], ['OUTBOUNDED', '已出库', 'success'], ['CANCELLED', '已取消', 'danger'], ['FUTURE_STATE', '未知状态', 'neutral'], ] @@ -38,7 +38,7 @@ describe('OutboundsView permissions and navigation', () => { beforeEach(() => { vi.stubGlobal('ResizeObserver', ResizeObserverStub) vi.stubGlobal('localStorage', { getItem: () => null, setItem: () => undefined, removeItem: () => undefined }) - vi.mocked(listOutbounds).mockReset().mockResolvedValue({ records: [{ id: '1', outboundNo: 'OUT-1', dispatchNo: 'PC-1', status: 'PICKED', palletCount: 1, packageCount: 2 }], page: 1, size: 20, total: 1, pages: 1 }) + vi.mocked(listOutbounds).mockReset().mockResolvedValue({ records: [{ id: '1', outboundNo: 'OUT-1', dispatchNo: 'PC-1', status: 'WAIT_PICK', palletCount: 1, packageCount: 2 }], page: 1, size: 20, total: 1, pages: 1 }) canPermission.mockReset().mockReturnValue(true) routerPush.mockReset() }) diff --git a/src/shared/components/StatusBadge.vue b/src/shared/components/StatusBadge.vue index 2eb02cb2..cd737b4b 100644 --- a/src/shared/components/StatusBadge.vue +++ b/src/shared/components/StatusBadge.vue @@ -5,7 +5,7 @@ const props = defineProps<{ status: string }>() const tone = computed(() => { if (['正常', '启用', '已完成', '已审核', '审核通过', '已入库', '已出库', '装柜完成', '派送完成', '已确认', '已放舱'].includes(props.status)) return 'success' - if (['待审核', '草稿', '待处理', '待分配', '未提交', '未录入', '待测量数据', '待分柜', '待装柜', '预装柜中', '待出库', '已拣货', '变更中'].includes(props.status)) return 'warning' + if (['待审核', '草稿', '待处理', '待分配', '未提交', '未录入', '待测量数据', '待分柜', '待装柜', '预装柜中', '待拣货', '待出库', '已拣货', '变更中'].includes(props.status)) return 'warning' if (['已受理', '已订舱', '已提交', '运输中', '处理中', '拣货中', '装柜中'].includes(props.status)) return 'info' if (['停用', '已取消', '已驳回', '审核驳回', '失败', '订舱失败'].includes(props.status)) return 'danger' return 'neutral'