From 3e77c3976e6de9d0d703aea7dc1ee196fe94f30d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 22 Oct 2026 11:56:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/business-index.md | 2 +- .../components/ContainerCustomsPanel.test.ts | 4 +- .../components/ContainerCustomsPanel.vue | 2 +- src/modules/customers/customerDensity.css | 2 + .../customers/pages/CustomersView.test.ts | 6 +- src/modules/customers/pages/CustomersView.vue | 19 +- src/modules/ocean-bookings/AGENTS.md | 6 +- .../ocean-bookings/PROTOTYPE_MATRIX.md | 13 + .../api/platformOceanBookings.test.ts | 13 +- .../api/platformOceanBookings.ts | 12 +- .../OceanBookingPrintDialog.test.ts | 21 + .../components/OceanBookingPrintDialog.vue | 16 +- .../OceanBookingDetailView.release.test.ts | 108 ++++- .../pages/OceanBookingDetailView.test.ts | 17 +- .../pages/OceanBookingDetailView.vue | 439 ++++++++++++++++-- src/modules/ocean-bookings/types.ts | 26 +- src/modules/ocean-bookings/workflow.test.ts | 77 +++ src/modules/ocean-bookings/workflow.ts | 50 ++ .../components/DriverEditorDialog.vue | 8 +- .../overseas-warehouse/PROTOTYPE_MATRIX.md | 10 +- .../components/DeliveryDocumentsDialog.vue | 2 +- .../DispatchDocumentsDialog.test.ts | 30 +- .../components/DispatchDocumentsDialog.vue | 11 +- .../DispatchLoadingDocuments.test.ts | 4 + .../components/DispatchLoadingDocuments.vue | 35 +- .../DispatchVehicleDocuments.test.ts | 4 + .../components/DispatchVehicleDocuments.vue | 27 +- .../SmartDispatchDialog.layout.test.ts | 29 +- .../SmartDispatchDialog.planning.test.ts | 11 +- .../components/SmartDispatchDialog.test.ts | 17 +- .../components/SmartDispatchDialog.vue | 31 +- .../pages/InboundOrdersView.vue | 2 +- .../pages/LastMileOrdersView.vue | 2 +- .../pages/PalletsView.test.ts | 10 +- .../overseas-warehouse/pages/PalletsView.vue | 27 +- .../pages/UnifiedOrdersView.requests.test.ts | 22 + .../pages/UnifiedOrdersView.test.ts | 2 +- .../pages/UnifiedOrdersView.vue | 74 ++- src/styles/adminDensity.css | 5 + src/styles/adminDensity.test.ts | 21 + 40 files changed, 976 insertions(+), 241 deletions(-) create mode 100644 src/modules/ocean-bookings/PROTOTYPE_MATRIX.md create mode 100644 src/modules/ocean-bookings/workflow.test.ts create mode 100644 src/modules/ocean-bookings/workflow.ts diff --git a/docs/business-index.md b/docs/business-index.md index dd0ebd80..0aa1c069 100644 --- a/docs/business-index.md +++ b/docs/business-index.md @@ -37,7 +37,7 @@ | 货柜双工作台、排柜、预装/装柜清单、真实柜号后补与整柜报关 | 国内 `modules/container-loading/pages/DomesticContainersView.vue`、海外 `OverseasContainersView.vue`,共享详情实现 `ContainerDetailView.vue`;预装详情 `modules/loading-lists/pages/PreloadPlanDetailView.vue`,资料/PDA 证据 `components/ContainerOverviewPanel.vue`,报关 `components/ContainerCustomsPanel.vue` | 规则见下文「货柜工作台规则」 | | 合柜报关 / 同柜不可拆组 | `modules/unified-orders/api/coLoadGroups.ts`、`pages/UnifiedOrdersView.vue`;装柜消费 `modules/container-loading/pages/SmartLoadingWorkbenchView.vue` | 前端只提交订单 ID;Backend `firstleg/loading/coload/` 展开并冻结每票全部可用入仓片段。成员数是入仓片段数,不等于订单票数;只要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均整组处理 | | 全局智能装柜工作台、入仓码候选与箱数分配 | `modules/container-loading/components/SmartLoadingDialog.vue` 内嵌 `pages/GlobalSmartLoadingView.vue`,柜次页 `SmartLoadingWorkbenchView.vue`,以及 `smartLoadingChannelGroups.ts`、`smartLoadingItems.ts`、`api/platformContainerLoading.ts`;全局页当前不注册独立路由 | Backend `firstleg/` 的 `/inbound-candidates`、`/containers`、`/inbound-assignments`、`/plans/{planId}/pending-review-assignments`、`/group-assignments`、`/unpicked-items`。`orderStatuses` 最多四项且未传表示全部未完成状态;候选状态多选覆盖待审核、待测量、待分柜、装柜中,状态只用于筛选展示,按钮资格以服务端 `canAssign` 为准;待审核/待测量可提前占位但不得提前确认预装或启动 PDA。计划箱数编辑上限、已用和未用展示只消费服务端 `totalPackageCount/remainingPackageCount`,不得从候选页或本地规格重新拼算。人工加入允许跨渠道,智能推荐仍按柜次渠道。柜次容量和限重取服务端字段;同柜组作为单卡整组分页、分配和移除,任一成员已拣时整组保留 | -| 国内货柜履约订舱资料、打印、关联候选与出仓前船期 | `modules/ocean-bookings/` 的 `components/OceanBookingDocumentsPanel.vue`、`OceanBookingPrintDialog.vue`,以及 `modules/container-loading/`、`modules/port-schedules/` | 订舱草稿按当前版本维护资料,放舱后资料与表单只读;打印对草稿与已放舱订舱均开放,按当前已保存资料生成,未填写字段显示“—”。`platformOceanBookings.ts` 的 `/link-candidates` 只消费服务端分页,`containerCount` 是柜型需求 `quantity` 总和;Backend `firstleg/oceanbooking/`、`file/`、`print/`、`firstleg/portschedule/` | +| 国内货柜履约订舱资料、打印、关联候选与出仓前船期 | `modules/ocean-bookings/` 的 `components/OceanBookingDocumentsPanel.vue`、`OceanBookingPrintDialog.vue`,以及 `modules/container-loading/`、`modules/port-schedules/` | 订舱草稿按当前版本维护资料,放舱后运输/预报/关系方/柜型/附件只读;客服实货量经 `/actual-cargo` 按编辑权限、版本、幂等键和原因补录;打印对草稿与已放舱订舱均开放,按当前已保存资料生成,托运单取委值,提单确认件取手工实值,未填显示“—”且不以委值回退。`platformOceanBookings.ts` 的 `/link-candidates` 只消费服务端分页,`containerCount` 是柜型需求 `quantity` 总和;Backend `firstleg/oceanbooking/`、`file/`、`print/`、`firstleg/portschedule/` | | 订舱级拖车预约 | `modules/trucking/api/platformTrucking.ts` 的 `/candidates`、`components/TruckAppointmentForm.vue`、`pages/TruckAppointmentCreateView.vue`、`TruckAppointmentsView.vue` | 表单只选择服务端返回的 `RELEASED` 订舱与 `TRUCKING + truckingScope=DOMESTIC` 国内拖车供应商,不选择或提交客户、柜次、装柜计划或真实集装箱号;清关提柜固定选择 `OVERSEAS` 海外车队,类型在供应商主档单选维护;提单号和仓库由订舱冻结事实带入。一个订舱最多一张非终态拖车单,重复创建转入现有单继续编辑;PDA 完成不自动推进拖车。附件为平台作用域且不重复提单文件;Backend `TruckAppointmentService`、`truck-appointments-v1.yaml` | | 目的港码头拖车与还空预警 | 海外清关提柜工作台/详情 `modules/container-loading/pages/OverseasContainersView.vue`、`OverseasContainerDetailView.vue`,API `modules/container-loading/api/terminalDrayages.ts`、`containerArrivalTimes.ts` | 与订舱级拖车预约是不同生命周期;按 `containerBookingId` 读取服务端状态、`allowedActions` 和历史,不能复用订舱级拖车表单或自行推进状态。列表“预计到仓”列直接提供人工日期时间选择器,保存时通过 Lastleg 柜级版本和幂等键原子更新该柜全部尾程订单,已有入库计划后禁止修改;“实际到仓”只读 `ll_receiving_container_session.receiving_at`,即海外 PDA 首次开始收货时间。还空截止、超时天/小时只展示服务端基于权威卸柜完成时间加 72 小时计算的结果;Backend `firstleg/terminaldrayage/`、`lastleg-service/`,邮件记录通过 `terminalDrayages.ts` 查询与重试。 | | 历史报关协同与国内出仓 | `modules/customs/`、`modules/outbound-dispatch/` | `modules/customs/` 是历史报关案件/清单/代理提交与回执入口;装柜完成后的整柜退税/买单资料从货柜详情进入,不在旧模块复制;Backend `firstleg/outbound/dispatch/`、`firstleg/customs/`、`file/` | diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts index ce80b16a..dd31c346 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts +++ b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts @@ -114,12 +114,12 @@ describe('ContainerCustomsPanel', () => { wrapper.unmount() }) - it('开始报关确认显示柜次号,取消不会冻结资料或调用开始接口', async () => { + it('开始报关确认使用简短文案,取消不会冻结资料或调用开始接口', async () => { const confirm = vi.spyOn(ElMessageBox, 'confirm').mockRejectedValueOnce('cancel') const wrapper = await render('READY_TO_START') await wrapper.findAll('button').find(button => button.text() === '开始报关')!.trigger('click') await flushPromises() - expect(confirm).toHaveBeenCalledWith(expect.any(String), '开始整柜报关 · MCR08290001', expect.objectContaining({ customClass: 'admin-density-confirm' })) + expect(confirm).toHaveBeenCalledWith('确认开始整柜报送?', '整柜报关', expect.objectContaining({ customClass: 'admin-density-confirm', confirmButtonText: '确认' })) expect(api.start).not.toHaveBeenCalled() confirm.mockRestore() wrapper.unmount() diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.vue b/src/modules/container-loading/components/ContainerCustomsPanel.vue index 46549a02..64df6f81 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.vue +++ b/src/modules/container-loading/components/ContainerCustomsPanel.vue @@ -183,7 +183,7 @@ async function resolveFormalResult(batch: DeclarationBatch, result: DeclarationB async function start() { if (!customs.value || customs.value.status !== 'READY_TO_START') return try { - await ElMessageBox.confirm('开始后将冻结客户当前退税资料版本、关联入仓码覆盖范围及平台买单准备事实,确认开始整柜报关?', customsDialogTitle('开始整柜报关'), { customClass: 'admin-density-confirm', type: 'warning', confirmButtonText: '确认开始' }) + await ElMessageBox.confirm('确认开始整柜报送?', '整柜报关', { customClass: 'admin-density-confirm', type: 'warning', confirmButtonText: '确认' }) } catch { return } actionLoading.value = true; actionError.value = null try { customs.value = await startContainerCustoms(props.loadingPlanId, customs.value.version); ElMessage.success('整柜报关已开始') } diff --git a/src/modules/customers/customerDensity.css b/src/modules/customers/customerDensity.css index 69d905ac..dddb825e 100644 --- a/src/modules/customers/customerDensity.css +++ b/src/modules/customers/customerDensity.css @@ -59,6 +59,8 @@ /* Customer dialog business geometry; common appearance stays in adminDensity.css. */ +.customer-editor-dialog .density-form--outlined { --el-border-radius-base: var(--cc-radius-lg); } +.customer-editor-dialog .density-form--outlined .el-input-number .el-input__inner { text-align: left; } .customer-editor-dialog .customer-editor-table { table-layout: fixed; } .customer-editor-dialog .customer-editor-table td:not([colspan]) { width: calc(100% / 3); } .customer-review-table th { width: 120px; } diff --git a/src/modules/customers/pages/CustomersView.test.ts b/src/modules/customers/pages/CustomersView.test.ts index 6b9c03b0..bccea54e 100644 --- a/src/modules/customers/pages/CustomersView.test.ts +++ b/src/modules/customers/pages/CustomersView.test.ts @@ -182,7 +182,7 @@ describe('CustomersView', () => { it('searches the customer list by customer name only', () => { expect(customersSource).toContain("label: '客户名称', placeholder: '请输入客户名称'") expect(customersSource).toContain("label: '客户昵称'") - expect(customersSource).toContain('中文拼音大写首字母,如 SZWL') + expect(customersSource).toContain('placeholder="选填,默认自动生成"') expect(customersSource).not.toContain('公司名称、客户编号或联系人') expect(customersSource).not.toContain('row.tenantCode.toLowerCase().includes(keyword)') expect(customersSource).not.toContain('String(row.id).includes(keyword)') @@ -210,12 +210,16 @@ describe('CustomersView', () => { expect(document.querySelector('.customer-editor-dialog .qualification-credit-code .el-form-item__label')?.style.width).toBe('144px') const company = document.querySelector('.customer-editor-dialog input[placeholder="请输入工商登记公司名称"]')! expect(company.closest('.el-form-item')?.querySelector('.el-form-item__label')?.style.width).toBe('120px') + const legalName = document.querySelector('.customer-editor-dialog input[placeholder="请输入营业执照上的企业名称"]')! + expect(legalName.closest('.el-form-item')?.querySelector('.el-form-item__label')?.style.width).toBe('144px') + expect(company.closest('form')?.classList.contains('density-form--outlined')).toBe(true) company.value = '' company.dispatchEvent(new Event('input', { bubbles: true })) await flushPromises() company.dispatchEvent(new FocusEvent('blur')) await vi.waitFor(() => expect(company.closest('.el-form-item')?.querySelector('.el-form-item__error')?.textContent).toContain('请输入公司名称')) expect(document.querySelector('.customer-editor-dialog .el-dialog__footer')?.textContent).toContain('保存资料和资质') + expect(document.querySelector('.customer-editor-dialog .el-dialog__footer')?.textContent).not.toContain('保存资料时会一并上传已选资质') expect(updatePlatformTenant).not.toHaveBeenCalled() }) diff --git a/src/modules/customers/pages/CustomersView.vue b/src/modules/customers/pages/CustomersView.vue index 58b79d13..a294a5f0 100644 --- a/src/modules/customers/pages/CustomersView.vue +++ b/src/modules/customers/pages/CustomersView.vue @@ -1085,10 +1085,10 @@ onMounted(() => { void loadCustomers() }) - +

基础资料

- + @@ -1103,8 +1103,8 @@ onMounted(() => { void loadCustomers() })
-

企业认证资料

营业执照和18位统一社会信用代码为必填业务项;法人身份证、货代资质证书和银行开户许可证均为选填。

支持 PDF、JPG、PNG,单个文件不超过 5MB;* 为提交审核必填项。

{{ statusMeta(editorStatus).label }}
-
{{ form.unifiedSocialCreditCode }}{{ creditCodeEstablished ? '已登记的统一社会信用代码不可直接修改。' : '必须与营业执照一致,并随营业执照共同保存;法人身份证为选填。' }}
+

企业认证资料

营业执照和18位统一社会信用代码为必填业务项;法人身份证、货代资质证书和银行开户许可证均为选填。

支持 PDF、JPG、PNG,单个文件不超过 5MB;* 为提交审核必填项。

{{ statusMeta(editorStatus).label }}

+
{{ form.unifiedSocialCreditCode }}必须与营业执照一致,并随营业执照共同保存;法人身份证为选填。
文件类型上传文件有效期
@@ -1124,7 +1124,7 @@ onMounted(() => { void loadCustomers() })
- + @@ -1183,13 +1183,12 @@ onMounted(() => { void loadCustomers() })