Merge branch 'release/test' of http://8.163.30.128:10082/html/admin-web into release/test

This commit is contained in:
2026-09-03 21:09:42 +08:00
3 changed files with 68 additions and 23 deletions
+3 -3
View File
@@ -46,15 +46,15 @@
| 消息中心 | `modules/notifications/` | Backend `iam/.../notification/`;仅顶部消息中心,不进入左侧菜单 | | 消息中心 | `modules/notifications/` | Backend `iam/.../notification/`;仅顶部消息中心,不进入左侧菜单 |
| 订单列表、详情、商品图片与入库费用 | `modules/unified-orders/`、`modules/first-leg-orders/`;平台商品图片由 `modules/unified-orders/api/unifiedOrders.ts` 按客户与 fileId 下载 | `order/`、`firstleg/`、Finance;图片读取归 `file/` 的 `PlatformProductImageController`。Order 状态为 `SUBMITTED` 时统一列表必须保持 `PENDING_REVIEW/APPROVE_ORDER`,不得被已存在的 Firstleg 准备中入库投影覆盖 | | 订单列表、详情、商品图片与入库费用 | `modules/unified-orders/`、`modules/first-leg-orders/`;平台商品图片由 `modules/unified-orders/api/unifiedOrders.ts` 按客户与 fileId 下载 | `order/`、`firstleg/`、Finance;图片读取归 `file/` 的 `PlatformProductImageController`。Order 状态为 `SUBMITTED` 时统一列表必须保持 `PENDING_REVIEW/APPROVE_ORDER`,不得被已存在的 Firstleg 准备中入库投影覆盖 |
| 客户原始申报资料编辑与渠道变更 | `modules/unified-orders/components/OrderDeclarationEditorDialog.vue`、`api/unifiedOrders.ts`、`pages/UnifiedOrdersView.vue` | 使用订单版本和货物 `itemNo` 整体提交,不允许增删货物行;只编辑客户申报快照,不覆盖仓库/PDA 实测、测量审核或计费事实。渠道变化需二次确认并由 Backend `order/` 联查 `firstleg/` 的分柜、同柜、装柜与重新计价门禁;国内收货仓或中转仓变化必须重新下单 | | 客户原始申报资料编辑与渠道变更 | `modules/unified-orders/components/OrderDeclarationEditorDialog.vue`、`api/unifiedOrders.ts`、`pages/UnifiedOrdersView.vue` | 使用订单版本和货物 `itemNo` 整体提交,不允许增删货物行;只编辑客户申报快照,不覆盖仓库/PDA 实测、测量审核或计费事实。渠道变化需二次确认并由 Backend `order/` 联查 `firstleg/` 的分柜、同柜、装柜与重新计价门禁;国内收货仓或中转仓变化必须重新下单 |
| 入库、测量与计价主数据 | `modules/inbounds/`、`modules/fba-pricing/`、`modules/dimension-config/`、`modules/fee-items/` | `firstleg/`、`order/`、`file/`、`integration-service/` | | 入库、测量、客户箱规策略与计价主数据 | `modules/inbounds/measurement/`、`modules/fba-pricing/`、`modules/dimension-config/`、`modules/customers/components/CustomerBillingRulesPanel.vue`、`modules/fee-items/` | 客户固定四档箱规,默认计算方式为 `ADD/MULTIPLY`;测量行可选实际尺寸、箱规尺寸策略或客户默认尺寸系数,保存后以 Backend `firstleg/` 返回的外箱尺寸、体积重和计费基数为准,不在页面独立重算 |
| 财务与柜级公共费用分摊 | `modules/finance/`,继续读该目录 `AGENTS.md`;柜费入口 `modules/loading-lists/components/ContainerFeesPanel.vue`、`api/containerFees.ts` | `finance/`、`firstleg/`、`file/`、`docs/财务管理/`;跨客户公共柜费只按服务端柜内客户/订单事实分摊,金额必须守恒,创建与审核、驳回、调整、作废均整组原子处理 | | 财务与柜级公共费用分摊 | `modules/finance/`,继续读该目录 `AGENTS.md`;柜费入口 `modules/loading-lists/components/ContainerFeesPanel.vue`、`api/containerFees.ts` | `finance/`、`firstleg/`、`file/`、`docs/财务管理/`;跨客户公共柜费只按服务端柜内客户/订单事实分摊,金额必须守恒,创建与审核、驳回、调整、作废均整组原子处理 |
| 货柜、排柜、预装/装柜清单、真实柜号后补与整柜报关 | `modules/container-loading/`、`modules/loading-lists/`;预装详情 `pages/PreloadPlanDetailView.vue`,货柜详情 `pages/ContainerDetailView.vue`,资料/PDA 证据 `components/ContainerOverviewPanel.vue`,报关 `components/ContainerCustomsPanel.vue`、`api/platformContainerCustoms.ts` | 创建柜次时自动生成唯一预装计划,加入入仓码即同步进入该计划,页面不提供创建计划或推送预装操作。预装详情先调用 `/loading-plans/{planId}/lifecycle`。订舱关联和预装确认允许真实集装箱号为空;首次补录可在 `EDITING/LOADING` 进行,PDA 最终完成前必须补齐;已有柜号纠正只允许 `EDITING` 且任务未领取、无物理作业事实。整柜报关逐资料完成,全买单柜使用 `complete-all-buy` | | 货柜、排柜、预装/装柜清单、真实柜号后补与整柜报关 | `modules/container-loading/`、`modules/loading-lists/`;预装详情 `pages/PreloadPlanDetailView.vue`,货柜详情 `pages/ContainerDetailView.vue`,资料/PDA 证据 `components/ContainerOverviewPanel.vue`,报关 `components/ContainerCustomsPanel.vue`、`api/platformContainerCustoms.ts` | 创建柜次时自动生成唯一预装计划,加入入仓码即同步进入该计划,页面不提供创建计划或推送预装操作。预装详情先调用 `/loading-plans/{planId}/lifecycle`。订舱关联和预装确认允许真实集装箱号为空;首次补录可在 `EDITING/LOADING` 进行,PDA 最终完成前必须补齐;已有柜号纠正只允许 `EDITING` 且任务未领取、无物理作业事实。整柜报关逐资料完成,全买单柜使用 `complete-all-buy` |
| 合柜报关 / 同柜不可拆组 | `modules/unified-orders/api/coLoadGroups.ts`、`pages/UnifiedOrdersView.vue`;装柜消费 `modules/container-loading/pages/SmartLoadingWorkbenchView.vue` | 前端只提交订单 ID;Backend `firstleg/loading/coload/` 展开并冻结每票全部可用入仓片段。成员数是入仓片段数,不等于订单票数;只要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均整组处理 | | 合柜报关 / 同柜不可拆组 | `modules/unified-orders/api/coLoadGroups.ts`、`pages/UnifiedOrdersView.vue`;装柜消费 `modules/container-loading/pages/SmartLoadingWorkbenchView.vue` | 前端只提交订单 ID;Backend `firstleg/loading/coload/` 展开并冻结每票全部可用入仓片段。成员数是入仓片段数,不等于订单票数;只要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均整组处理 |
| 智能装柜按入仓码候选与箱数分配 | `modules/container-loading/pages/SmartLoadingWorkbenchView.vue`、`api/platformContainerLoading.ts` | Backend `firstleg/` 的 `PlatformSmartLoadingController`、`SmartLoadingService`;`GlobalSmartLoadingView.vue` 当前未注册路由,不作为正式入口 | | 全局智能装柜工作台、入仓码候选与箱数分配 | `modules/container-loading/components/SmartLoadingDialog.vue` 内嵌 `pages/GlobalSmartLoadingView.vue`,柜次页 `SmartLoadingWorkbenchView.vue`,以及 `smartLoadingChannelGroups.ts`、`api/platformContainerLoading.ts`;全局页当前不注册独立路由 | Backend `firstleg/` 的 `/inbound-candidates`、`/containers` 和计划分配接口;渠道分组选项与服务端映射必须一致,同柜不可拆组作为单个候选卡片整组分配,前端不得拆成员分页或自行拼组 |
| 三期订舱资料、打印、关联候选与出仓前船期 | `modules/ocean-bookings/` 的 `components/OceanBookingDocumentsPanel.vue`、`OceanBookingPrintDialog.vue`,以及 `modules/container-loading/`、`modules/port-schedules/` | 订舱草稿按当前版本维护资料,放舱后资料与表单只读;打印只对 `RELEASED` 开放。`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/` | 订舱草稿按当前版本维护资料,放舱后资料与表单只读;打印只对 `RELEASED` 开放。`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` 订舱与拖车供应商,不选择或提交客户、柜次、装柜计划或真实集装箱号;提单号和仓库由订舱冻结事实带入。一个订舱最多一张非终态拖车单,重复创建转入现有单继续编辑;PDA 完成不自动推进拖车。附件为平台作用域且不重复提单文件;Backend `TruckAppointmentService`、`truck-appointments-v1.yaml` | | 订舱级拖车预约 | `modules/trucking/api/platformTrucking.ts` 的 `/candidates`、`components/TruckAppointmentForm.vue`、`pages/TruckAppointmentCreateView.vue`、`TruckAppointmentsView.vue` | 表单只选择服务端返回的 `RELEASED` 订舱与拖车供应商,不选择或提交客户、柜次、装柜计划或真实集装箱号;提单号和仓库由订舱冻结事实带入。一个订舱最多一张非终态拖车单,重复创建转入现有单继续编辑;PDA 完成不自动推进拖车。附件为平台作用域且不重复提单文件;Backend `TruckAppointmentService`、`truck-appointments-v1.yaml` |
| 历史报关协同与国内出仓 | `modules/customs/`、`modules/outbound-dispatch/` | `modules/customs/` 是历史报关案件/清单/代理提交与回执入口;装柜完成后的整柜退税/买单资料从货柜详情进入,不在旧模块复制;Backend `firstleg/outbound/dispatch/`、`firstleg/customs/`、`file/` | | 历史报关协同与国内出仓 | `modules/customs/`、`modules/outbound-dispatch/` | `modules/customs/` 是历史报关案件/清单/代理提交与回执入口;装柜完成后的整柜退税/买单资料从货柜详情进入,不在旧模块复制;Backend `firstleg/outbound/dispatch/`、`firstleg/customs/`、`file/` |
| 主数据、下载、审计 | `modules/master-data/`、`modules/downloads/`、`modules/audit/` | `warehouse/`、`file/`、`audit-service/` | | 主数据、下载、退避任务重试、审计 | `modules/master-data/`、`modules/downloads/pages/DownloadCenterView.vue`、`modules/audit/` | File 导出任务返回 `attemptCount/maxAttempts/nextRetryAt`;仅失败或已进入自动退避的等待任务显示重试,等待任务重试用于立即执行当前投递,不创建新任务;联查 `warehouse/`、`file/`、`audit-service/` |
| 四期海外接柜、入库与库存 | `modules/overseas-warehouse/` | `gateway-overseas/`、`lastleg-service/`、`warehouse-overseas-service/`;不含出库和派送 | | 四期海外接柜、入库与库存 | `modules/overseas-warehouse/` | `gateway-overseas/`、`lastleg-service/`、`warehouse-overseas-service/`;不含出库和派送 |
| 四期海外采购、卡车、散板 | `modules/overseas-business/` | `gateway-overseas/`、`lastleg-service/` | | 四期海外采购、卡车、散板 | `modules/overseas-business/` | `gateway-overseas/`、`lastleg-service/` |
| 四期合作仓与通用资料 | 合作仓 `modules/warehouses/`;报价/汇率/车辆 `modules/overseas-master-data/` | 国内 `warehouse/`、海外实际资源与服务 | | 四期合作仓与通用资料 | 合作仓 `modules/warehouses/`;报价/汇率/车辆 `modules/overseas-master-data/` | 国内 `warehouse/`、海外实际资源与服务 |
@@ -95,7 +95,6 @@ function containerPage(records: ContainerBookingResource[] = [container]) {
function mockContainerSearch(records: ContainerBookingResource[]) { function mockContainerSearch(records: ContainerBookingResource[]) {
vi.mocked(listSmartLoadingWorkbenchContainers).mockImplementation(async (query) => containerPage(records.filter((item) => ( vi.mocked(listSmartLoadingWorkbenchContainers).mockImplementation(async (query) => containerPage(records.filter((item) => (
item.planStatus !== 'COMPLETED' item.planStatus !== 'COMPLETED'
&& (!query.warehouseId || item.warehouseId === query.warehouseId)
&& query.channelGroupCodes.includes(resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code || '') && query.channelGroupCodes.includes(resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code || '')
)))) ))))
} }
@@ -259,7 +258,7 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.text()).not.toContain('美森正班海派') expect(wrapper.text()).not.toContain('美森正班海派')
}) })
it('moves the shipping warehouse into the query filters', async () => { it('keeps the shipping warehouse as a candidate-only query filter', async () => {
const dongguanContainer = { const dongguanContainer = {
...container, ...container,
id: '9007199254740998', id: '9007199254740998',
@@ -277,16 +276,18 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.text()).toContain('MSCU-1') expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).toContain('MSCU-DG') expect(wrapper.text()).toContain('MSCU-DG')
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' }) wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
await flushPromises() await flushPromises()
expect(wrapper.text()).toContain('MSCU-DG') expect(wrapper.text()).toContain('MSCU-DG')
expect(wrapper.text()).not.toContain('MSCU-1') expect(wrapper.text()).toContain('MSCU-1')
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: dongguanContainer.warehouseId, page: 1, size: 20 }) expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: dongguanContainer.warehouseId, page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
}) })
it('keeps the selected channel when the warehouse changes even if that warehouse has no matching container', async () => { it('keeps channel cabinets unchanged when another search field has no matching cabinet', async () => {
const dongguanOvertimeContainer = { const dongguanOvertimeContainer = {
...container, ...container,
id: '9007199254740998', id: '9007199254740998',
@@ -302,15 +303,49 @@ describe('GlobalSmartLoadingView', () => {
mockContainerSearch([container, dongguanOvertimeContainer]) mockContainerSearch([container, dongguanOvertimeContainer])
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises() await flushPromises()
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' }) wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
await flushPromises() await flushPromises()
expect(wrapper.find('.channel-tags .el-checkbox-button.is-checked').text()).toBe('美森正班') expect(wrapper.find('.channel-tags .el-checkbox-button.is-checked').text()).toBe('美森正班')
expect(wrapper.findAll('.container-option')).toHaveLength(0) expect(wrapper.findAll('.container-option')).toHaveLength(1)
expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).not.toContain('MSCU-DG-MAX') expect(wrapper.text()).not.toContain('MSCU-DG-MAX')
expect(listSmartLoadingCandidates).toHaveBeenCalledTimes(2) expect(listSmartLoadingCandidates).toHaveBeenCalledTimes(2)
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: '18', page: 1, size: 20 }) expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: '18', page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
})
it('filters candidate details by inbound code without filtering channel cabinets', async () => {
const secondContainer = {
...container,
id: '9007199254740998',
planId: '9007199254740999',
cabinetNo: 'CB-2',
containerNo: 'MSCU-2',
}
vi.mocked(listContainers).mockResolvedValue({ records: [container, secondContainer], page: 1, size: 100, total: 2, pages: 1 })
mockContainerSearch([container, secondContainer])
vi.mocked(getSmartLoadingPlanByBooking).mockImplementation(async (bookingId) => ({ ...plan, bookingId }))
vi.mocked(listSmartLoadingCandidates)
.mockResolvedValueOnce(candidatePage([candidate]))
.mockResolvedValueOnce(candidatePage([{ ...candidate, inboundCode: 'NYEXX2609030002' }]))
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises()
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
warehouseId: '', customerId: '', inboundCode: 'NYEXX2609030002', expectedArrivalRange: [], orderStatus: '',
})
await flushPromises()
expect(wrapper.get('.candidate-pane').text()).toContain('NYEXX2609030002')
expect(wrapper.findAll('.container-option')).toHaveLength(2)
expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).toContain('MSCU-2')
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], inboundCode: 'NYEXX2609030002', page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
}) })
it('sends the active name-only customer selector to the server candidate query', () => { it('sends the active name-only customer selector to the server candidate query', () => {
@@ -336,6 +371,7 @@ describe('GlobalSmartLoadingView', () => {
.mockResolvedValueOnce(candidatePage([{ ...candidate, inboundId: '51', inboundCode: 'IN-MATCH', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-11T08:00:00' }])) .mockResolvedValueOnce(candidatePage([{ ...candidate, inboundId: '51', inboundCode: 'IN-MATCH', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-11T08:00:00' }]))
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises() await flushPromises()
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.getComponent(QueryToolbar).vm.$emit('search', { wrapper.getComponent(QueryToolbar).vm.$emit('search', {
customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', expectedArrivalRange: ['2026-09-10', '2026-09-12'], customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', expectedArrivalRange: ['2026-09-10', '2026-09-12'],
@@ -347,7 +383,8 @@ describe('GlobalSmartLoadingView', () => {
expect(candidates).not.toContain('IN-WRONG-STATUS') expect(candidates).not.toContain('IN-WRONG-STATUS')
expect(candidates).not.toContain('IN-WRONG-DATE') expect(candidates).not.toContain('IN-WRONG-DATE')
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], expectedArrivalStart: '2026-09-10', expectedArrivalEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 20 }) expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], expectedArrivalStart: '2026-09-10', expectedArrivalEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], expectedArrivalStart: '2026-09-10', expectedArrivalEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 200 }) expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
expect(listSmartLoadingWorkbenchContainers).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], page: 1, size: 200 })
}) })
it('paginates large inbound candidate results with twenty rows by default', async () => { it('paginates large inbound candidate results with twenty rows by default', async () => {
@@ -219,19 +219,19 @@ const candidateColumns: DataTableColumn[] = [
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 },
{ key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 }, { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 },
// { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, // { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' },
{ key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 100, slotName: 'unitGrossWeight' }, { key: 'unitGrossWeightKg', label: '单箱毛重', width: 80, slotName: 'unitGrossWeight' },
{ key: 'totalGrossWeightKg', label: '总重量', width: 90, slotName: 'candidateTotalWeight' }, { key: 'totalGrossWeightKg', label: '重量', width: 90, slotName: 'candidateTotalWeight' },
{ key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 100, slotName: 'averageUnitVolume' }, { key: 'averageUnitVolumeCbm', label: '单箱体积', width: 80, slotName: 'averageUnitVolume' },
{ key: 'totalVolumeCbm', label: '总体积', width: 90, slotName: 'candidateTotalVolume' }, { key: 'totalVolumeCbm', label: '体积', width: 90, slotName: 'candidateTotalVolume' },
] ]
const itemColumns: DataTableColumn[] = [ const itemColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 90, fixed: 'left', slotName: 'actions' }, { key: 'actions', label: '操作', width: 80, fixed: 'left', slotName: 'actions' },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, fixed: 'left', showOverflowTooltip: true }, { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true },
{ key: 'coLoadGroup', label: '同柜不可拆', minWidth: 210, slotName: 'coLoadGroup' }, { key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' },
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 140 }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 },
{ key: 'packageCount', label: '分配箱数', width: 140, slotName: 'packageCount' }, { key: 'packageCount', label: '分配箱数', width: 100, slotName: 'packageCount' },
{ key: 'grossWeightKg', label: '毛重', width: 110, slotName: 'weight' }, { key: 'grossWeightKg', label: '毛重', width: 80, slotName: 'weight' },
{ key: 'volumeCbm', label: '体积', width: 105, slotName: 'volume' }, { key: 'volumeCbm', label: '体积', width: 80, slotName: 'volume' },
] ]
function formatNumber(value: number, digits = 3) { function formatNumber(value: number, digits = 3) {
@@ -306,6 +306,14 @@ function candidateQueryParams(): Parameters<typeof listSmartLoadingCandidates>[0
return params return params
} }
function containerQueryParams(page = 1): Parameters<typeof listSmartLoadingWorkbenchContainers>[0] {
return {
channelGroupCodes: [...selectedChannelCodes.value],
page,
size: 200,
}
}
async function loadCandidateData() { async function loadCandidateData() {
if (!selectedChannelCodes.value.length) return if (!selectedChannelCodes.value.length) return
const request = ++candidateRequest const request = ++candidateRequest
@@ -357,9 +365,9 @@ async function loadContainerData(preserved: Record<string, number> = {}) {
if (!selectedChannelCodes.value.length) return if (!selectedChannelCodes.value.length) return
const request = ++containerRequest const request = ++containerRequest
try { try {
const firstPage = await listSmartLoadingWorkbenchContainers({ ...candidateQueryParams(), page: 1, size: 200 }) const firstPage = await listSmartLoadingWorkbenchContainers(containerQueryParams())
const remainingPages = firstPage.pages > 1 const remainingPages = firstPage.pages > 1
? await Promise.all(Array.from({ length: firstPage.pages - 1 }, (_, index) => listSmartLoadingWorkbenchContainers({ ...candidateQueryParams(), page: index + 2, size: 200 }))) ? await Promise.all(Array.from({ length: firstPage.pages - 1 }, (_, index) => listSmartLoadingWorkbenchContainers(containerQueryParams(index + 2))))
: [] : []
if (request !== containerRequest) return if (request !== containerRequest) return
containers.value = [firstPage, ...remainingPages].flatMap((page) => page.records) containers.value = [firstPage, ...remainingPages].flatMap((page) => page.records)
@@ -431,7 +439,7 @@ async function selectChannels(codes: string[]) {
async function applyFilters(model: QueryModel) { async function applyFilters(model: QueryModel) {
appliedQuery.value = { ...model } appliedQuery.value = { ...model }
candidatePage.value = 1 candidatePage.value = 1
await loadWorkbenchData() await loadCandidateData()
} }
async function changeCandidatePage(page: number) { async function changeCandidatePage(page: number) {