From e75a0eb46392c5caf99787518e622af216075b89 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 20 Aug 2026 19:20:10 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/FinanceCustomerArapView.test.ts | 20 +++++++++++++++++-- .../finance/pages/FinanceCustomerArapView.vue | 13 ++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/modules/finance/pages/FinanceCustomerArapView.test.ts b/src/modules/finance/pages/FinanceCustomerArapView.test.ts index e41b5bf1..9cb1ddd5 100644 --- a/src/modules/finance/pages/FinanceCustomerArapView.test.ts +++ b/src/modules/finance/pages/FinanceCustomerArapView.test.ts @@ -114,6 +114,21 @@ describe('FinanceCustomerArapView G2 operations', () => { expect(editFinanceFeeSource).not.toHaveBeenCalled() }) + it('treats inbound and container fee types as Firstleg sources when sourceSystem is missing', async () => { + const legacyFirstleg = { ...feeSummary, sourceSystem: '', sourceType: 'CONTAINER_FEE', reviewStatus: 'APPROVED' as const } + vi.mocked(listFinanceFeeSources).mockResolvedValue({ records: [legacyFirstleg], total: 1, pages: 1 }) + const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:edit']) + await loadRows(wrapper) + + const buttonLabels = wrapper.findAll('button').map((button) => button.text()) + expect(buttonLabels).not.toContain('编辑') + expect(buttonLabels).not.toContain('调整') + const vm = wrapper.vm as unknown as { openMutation(row: Record, mode: 'ADJUST'): Promise } + await vm.openMutation(legacyFirstleg as unknown as Record, 'ADJUST') + expect(getFinanceFeeSource).not.toHaveBeenCalled() + expect(adjustFinanceFeeSource).not.toHaveBeenCalled() + }) + it('loads all customers by default and keeps explicit customer filtering', async () => { const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:customer:list']) expect(listFinanceFeeSources).toHaveBeenCalledWith(expect.objectContaining({ page: 1, size: 20, customerId: undefined, direction: undefined })) @@ -209,8 +224,9 @@ describe('FinanceCustomerArapView G2 operations', () => { expect(page).toContain('详情') expect(page).toContain("@click=\"openMutation(row, 'SUBMIT')\"") expect(page).toContain("@click=\"openMutation(row, 'ADJUST')\"") - expect(page).toContain("row.sourceSystem !== 'FIRSTLEG'") - expect(page).toContain("detail.sourceSystem !== 'FIRSTLEG'") + expect(page).toContain('!isFirstlegSource(row)') + expect(page).toContain('!isFirstlegSource(detail)') + expect(page).toContain("['INBOUND_FEE', 'CONTAINER_FEE'].includes(type)") expect(page).not.toMatch(/ - + @@ -531,7 +531,7 @@ onMounted(() => { void load(); void loadCustomers() })

{{ selectedCandidate.customerName || '客户待补充' }}

-

可用 {{ selectedCandidate.availablePackageCount }} 箱 · 单箱 {{ formatNumber(Number(selectedCandidate.unitWeightKg || 0), 1) }} kg · {{ formatNumber(Number(selectedCandidate.unitVolumeCbm || 0)) }} m³

+

可用 {{ selectedCandidate.availablePackageCount }} 箱 · 平均单箱毛重 {{ formatNumber(Number(selectedCandidate.unitGrossWeightKg || 0), 1) }} kg · 单箱体积 {{ formatNumber(Number(selectedCandidate.unitVolumeCbm || 0)) }} m³

diff --git a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts index 791e4fde..3d9a2762 100644 --- a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts +++ b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts @@ -37,7 +37,7 @@ describe('SmartLoadingWorkbenchView', () => { vi.clearAllMocks() vi.mocked(getContainer).mockResolvedValue(container) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue(planResponse) - vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsName: '服装', availablePackageCount: 12, unitWeightKg: 10, unitVolumeCbm: 0.2 }]) + vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsName: '服装', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, unitVolumeCbm: 0.2 }]) }) it('restores the real draft and explains candidate capacity', async () => { diff --git a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue index 03a0c5c5..6cc477f8 100644 --- a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue +++ b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue @@ -68,7 +68,7 @@ const candidateColumns: DataTableColumn[] = [ { key: 'goodsName', prop: 'goodsName', label: '货物', minWidth: 180 }, { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 110 }, { key: 'unitVolumeCbm', label: '单箱体积', width: 120, slotName: 'unitVolume' }, - { key: 'unitWeightKg', label: '单箱重量', width: 120, slotName: 'unitWeight' }, + { key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 140, slotName: 'unitGrossWeight' }, ] function formatNumber(value: number, digits = 3) { return Number.isFinite(value) ? value.toLocaleString('zh-CN', { maximumFractionDigits: digits }) : '0' } @@ -78,7 +78,7 @@ function itemRow(source: object) { return source as SmartLoadingItem } function explainCandidate(source: object) { const candidate = candidateRow(source) const volume = Number(candidate.unitVolumeCbm || 0) - const weight = Number(candidate.unitWeightKg || 0) + const weight = Number(candidate.unitGrossWeightKg || 0) const volumeRoom = volumeLimit.value ? Math.floor(Math.max(volumeLimit.value - totalVolume.value, 0) / Math.max(volume, 0.000001)) : candidate.availablePackageCount const weightRoom = weightLimit.value ? Math.floor(Math.max(weightLimit.value - totalWeight.value, 0) / Math.max(weight, 0.000001)) : candidate.availablePackageCount return `按 110% 上限,当前最多可装 ${Math.max(Math.min(candidate.availablePackageCount, volumeRoom, weightRoom), 0)} 箱` @@ -251,7 +251,7 @@ onMounted(load) - + diff --git a/src/modules/container-loading/types.ts b/src/modules/container-loading/types.ts index 505cb9cb..bef5174f 100644 --- a/src/modules/container-loading/types.ts +++ b/src/modules/container-loading/types.ts @@ -226,7 +226,8 @@ export interface SmartLoadingCandidate { customerName: string | null goodsName: string | null availablePackageCount: number - unitWeightKg: number | null + unitGrossWeightKg: number | null + unitNetWeightKg: number | null unitVolumeCbm: number | null } diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts index ffb32a39..ad9fdd6e 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts @@ -239,6 +239,9 @@ describe('UnifiedOrdersView contract', () => { it('binds dimension and weight evidence to their formal server records without mixing galleries', () => { expect(source).toContain(' {
+ + + @@ -1095,9 +1098,9 @@ onBeforeUnmount(() => { :customer-id="String(detail.summary.tenantId)" :inbound-id="String(detail.summary.inboundId)" :file-id="row.intelligentImageFileId" - :alt="`测量明细 ${row.itemNo || row.id} 的尺寸图片`" + :alt="`箱号 ${row.boxCode || row.itemNo || row.id} 的尺寸图片`" empty-text="未绑定尺寸图片" - @open="openMeasurementEvidence(row.intelligentImageFileId, `尺寸图片 · ${row.itemNo || row.id}`)" + @open="openMeasurementEvidence(row.intelligentImageFileId, `尺寸图片 · ${row.boxCode || row.itemNo || row.id}`)" /> From 19683f87597b4154bafbf8f280803300ee658684 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 21 Aug 2026 17:07:37 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/platformContainerLoading.test.ts | 11 +++++----- .../api/platformContainerLoading.ts | 11 +++++----- .../pages/GlobalSmartLoadingView.test.ts | 12 +++++------ .../pages/GlobalSmartLoadingView.vue | 17 ++++++++------- .../pages/SmartLoadingWorkbenchView.test.ts | 2 +- .../pages/SmartLoadingWorkbenchView.vue | 21 ++++++++++--------- src/modules/container-loading/types.ts | 6 +++--- 7 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/modules/container-loading/api/platformContainerLoading.test.ts b/src/modules/container-loading/api/platformContainerLoading.test.ts index 9bb21058..a17fe579 100644 --- a/src/modules/container-loading/api/platformContainerLoading.test.ts +++ b/src/modules/container-loading/api/platformContainerLoading.test.ts @@ -66,10 +66,10 @@ describe('platform container loading API', () => { it('normalizes every long id in candidates and plans', async () => { vi.mocked(http.get) .mockResolvedValueOnce({ code: '200', data: container }) - .mockResolvedValueOnce({ code: '200', data: [{ accountId: 1, inboundId: 2, inboundDetailId: 3, orderId: 4, warehouseId: 5, destinationWarehouseId: 6, channelCode: 'NY_EXX', channelName: '纽约EXX海铁', availablePackageCount: '7', unitGrossWeightKg: '12.5', unitNetWeightKg: '11.8', unitVolumeCbm: '0.187' }] }) + .mockResolvedValueOnce({ code: '200', data: [{ accountId: 1, inboundId: 2, orderId: 4, inboundCode: 'IN-1', warehouseId: 5, destinationWarehouseId: 6, channelCode: 'NY_EXX', channelName: '纽约EXX海铁', goodsSummary: '服装、鞋类', availablePackageCount: '7', unitGrossWeightKg: '12.5', unitNetWeightKg: '11.8', averageUnitVolumeCbm: '0.187', availableVolumeCbm: '1.309' }] }) expect((await getContainer('9')).id).toBe(String(container.id)) - expect(await listSmartLoadingCandidates('7', '5')).toMatchObject([{ accountId: '1', inboundDetailId: '3', destinationWarehouseId: '6', channelCode: 'NY_EXX', channelName: '纽约EXX海铁', availablePackageCount: 7, unitGrossWeightKg: 12.5, unitNetWeightKg: 11.8, unitVolumeCbm: 0.187 }]) - expect(http.get).toHaveBeenLastCalledWith('/platform/v1/first-leg/smart-loading/candidates', { params: { bookingId: '7', warehouseId: '5' } }) + expect(await listSmartLoadingCandidates('7', '5')).toMatchObject([{ accountId: '1', inboundId: '2', inboundCode: 'IN-1', destinationWarehouseId: '6', channelCode: 'NY_EXX', channelName: '纽约EXX海铁', goodsSummary: '服装、鞋类', availablePackageCount: 7, unitGrossWeightKg: 12.5, unitNetWeightKg: 11.8, averageUnitVolumeCbm: 0.187, availableVolumeCbm: 1.309 }]) + expect(http.get).toHaveBeenLastCalledWith('/platform/v1/first-leg/smart-loading/inbound-candidates', { params: { bookingId: '7', warehouseId: '5' } }) }) it('sends plan and item versions plus operation and idempotency keys', async () => { @@ -77,13 +77,14 @@ describe('platform container loading API', () => { vi.mocked(http.patch).mockResolvedValue({ code: '200', data: plan }) vi.mocked(http.delete).mockResolvedValue({ code: '200', data: plan }) const normalized = await createSmartLoadingPlan('7', null) - await assignSmartLoadingItem('10', { inboundId: '2', inboundDetailId: '3', packageCount: 4, expectedVersion: 5 }) + await assignSmartLoadingItem('10', { inboundId: '2', packageCount: 4, expectedVersion: 5 }) await adjustSmartLoadingItem('10', '11', 6, 7, 8) await removeSmartLoadingItem('10', '11', 9, 10) await pushSmartLoadingPreload('10', 11) expect(http.post).toHaveBeenNthCalledWith(1, '/platform/v1/first-leg/smart-loading/plans', { bookingId: '7', remark: null }) expect(normalized).toMatchObject({ id: String(plan.id), version: 3, canAssign: true, canEdit: true, pdaTaskId: String(plan.pdaTaskId), pdaTaskVersion: 4, items: [{ version: 1, pickedPackageCount: 2, minPackageCount: 2, canAdjust: true, canRemove: false }] }) - expect(vi.mocked(http.post).mock.calls[1]?.[1]).toMatchObject({ expectedVersion: 5, operationId: expect.stringContaining('assign-') }) + expect(vi.mocked(http.post).mock.calls[1]?.[0]).toBe('/platform/v1/first-leg/smart-loading/plans/10/inbound-assignments') + expect(vi.mocked(http.post).mock.calls[1]?.[1]).toMatchObject({ expectedVersion: 5, operationId: expect.stringContaining('assign-inbound-') }) expect(vi.mocked(http.patch).mock.calls[0]?.[1]).toMatchObject({ expectedPlanVersion: 7, expectedItemVersion: 8, operationId: expect.stringContaining('adjust-') }) expect(vi.mocked(http.delete).mock.calls[0]?.[1]).toMatchObject({ data: { expectedPlanVersion: 9, expectedItemVersion: 10, operationId: expect.stringContaining('remove-') } }) expect(vi.mocked(http.post).mock.calls[2]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.stringContaining('push-preload-') } }) diff --git a/src/modules/container-loading/api/platformContainerLoading.ts b/src/modules/container-loading/api/platformContainerLoading.ts index 2f7fa5f1..181a53e7 100644 --- a/src/modules/container-loading/api/platformContainerLoading.ts +++ b/src/modules/container-loading/api/platformContainerLoading.ts @@ -213,14 +213,15 @@ export async function completeContainerClearance(containerId: string, expectedVe } export async function listSmartLoadingCandidates(bookingId: string, warehouseId: string) { - const response = await http.get, ApiEnvelope>(`${smartLoadingPath}/candidates`, { params: { bookingId, warehouseId } }) + const response = await http.get, ApiEnvelope>(`${smartLoadingPath}/inbound-candidates`, { params: { bookingId, warehouseId } }) return unwrapApiResponse(response).map((raw) => ({ - ...raw, accountId: id(raw.accountId), inboundId: id(raw.inboundId), inboundDetailId: id(raw.inboundDetailId), + ...raw, accountId: id(raw.accountId), inboundId: id(raw.inboundId), orderId: id(raw.orderId), warehouseId: id(raw.warehouseId), destinationWarehouseId: optionalId(raw.destinationWarehouseId), availablePackageCount: numberValue(raw.availablePackageCount), unitGrossWeightKg: raw.unitGrossWeightKg == null ? null : numberValue(raw.unitGrossWeightKg), unitNetWeightKg: raw.unitNetWeightKg == null ? null : numberValue(raw.unitNetWeightKg), - unitVolumeCbm: raw.unitVolumeCbm == null ? null : numberValue(raw.unitVolumeCbm), + averageUnitVolumeCbm: raw.averageUnitVolumeCbm == null ? null : numberValue(raw.averageUnitVolumeCbm), + availableVolumeCbm: raw.availableVolumeCbm == null ? null : numberValue(raw.availableVolumeCbm), })) } @@ -239,8 +240,8 @@ export async function createSmartLoadingPlan(bookingId: string, remark: string | return plan(unwrapApiResponse(response)) } -export async function assignSmartLoadingItem(planId: string, payload: { inboundId: string; inboundDetailId: string; packageCount: number; expectedVersion: number }) { - const response = await http.post, ApiEnvelope>(`${smartLoadingPath}/plans/${encodeURIComponent(planId)}/assignments`, { ...payload, operationId: operationId('assign') }) +export async function assignSmartLoadingItem(planId: string, payload: { inboundId: string; packageCount: number; expectedVersion: number }) { + const response = await http.post, ApiEnvelope>(`${smartLoadingPath}/plans/${encodeURIComponent(planId)}/inbound-assignments`, { ...payload, operationId: operationId('assign-inbound') }) return plan(unwrapApiResponse(response)) } diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index 9324035d..3632a9a1 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -61,7 +61,7 @@ describe('GlobalSmartLoadingView', () => { vi.clearAllMocks() vi.mocked(listContainers).mockResolvedValue({ records: [container], page: 1, size: 100, total: 1, pages: 1 }) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue(plan) - vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '5', inboundDetailId: '6', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsName: '鞋类', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, unitVolumeCbm: 0.2 }]) + vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsSummary: '鞋类', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 2.4 }]) }) it('loads real containers, candidates and the selected plan into the three-column workbench', async () => { @@ -81,7 +81,7 @@ describe('GlobalSmartLoadingView', () => { expect(wrapper.text()).not.toContain('鞋类') expect(wrapper.text()).not.toContain('SKU-1') expect(wrapper.text()).not.toContain('IN-1') - expect(wrapper.text()).not.toContain('IN-2') + expect(wrapper.text()).toContain('IN-2') expect(wrapper.text()).not.toContain('ORD-1') expect(wrapper.text()).not.toContain('ORD-2') }) @@ -94,7 +94,7 @@ describe('GlobalSmartLoadingView', () => { total: 1, pages: 1, }) - vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '5', inboundDetailId: '6', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', warehouseId: '8', channelCode: 'EXX_RAIL_NY', channelName: '纽约EXX海铁', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsName: '鞋类', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, unitVolumeCbm: 0.2 }]) + vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', warehouseId: '8', channelCode: 'EXX_RAIL_NY', channelName: '纽约EXX海铁', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsSummary: '鞋类', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 2.4 }]) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() @@ -296,8 +296,8 @@ describe('GlobalSmartLoadingView', () => { it('matches candidate goods by their own formal top-level channel code', async () => { vi.mocked(listSmartLoadingCandidates).mockResolvedValue([ - { accountId: '1', inboundId: '5', inboundDetailId: '6', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-MATCH', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '匹配企业', goodsName: '匹配货物', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, unitVolumeCbm: 0.2 }, - { accountId: '2', inboundId: '8', inboundDetailId: '9', orderId: '10', orderNo: 'ORD-3', inboundCode: 'IN-OTHER', warehouseId: '8', channelCode: 'OTHER', channelName: '其他渠道', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '其他企业', goodsName: '其他货物', availablePackageCount: 8, unitGrossWeightKg: 10, unitNetWeightKg: 9, unitVolumeCbm: 0.2 }, + { accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-MATCH', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '匹配企业', goodsSummary: '匹配货物', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 2.4 }, + { accountId: '2', inboundId: '8', orderId: '10', orderNo: 'ORD-3', inboundCode: 'IN-OTHER', warehouseId: '8', channelCode: 'OTHER', channelName: '其他渠道', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '其他企业', goodsSummary: '其他货物', availablePackageCount: 8, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 1.6 }, ]) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() @@ -415,7 +415,7 @@ describe('GlobalSmartLoadingView', () => { }) it('limits adjusted package count to the assigned count plus remaining availability', async () => { - vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsName: '服装', availablePackageCount: 4, unitGrossWeightKg: 10, unitNetWeightKg: 9, unitVolumeCbm: 0.2 }]) + vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsSummary: '服装', availablePackageCount: 4, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 0.8 }]) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue index 6573be8f..1817ee1b 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue @@ -78,7 +78,7 @@ const activeChannel = computed(() => channelTabs.value.find((item) => item.code const editable = computed(() => plan.value?.canEdit === true) const assignable = computed(() => plan.value?.canAssign === true) const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null) -const assignedDetailIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundDetailId))) +const assignedInboundIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundId))) const totalPackages = computed(() => (plan.value?.items || []).reduce((sum, item) => sum + Number(item.packageCount || 0), 0)) const totalVolume = computed(() => (plan.value?.items || []).reduce((sum, item) => sum + Number(item.volumeCbm || 0), 0)) const totalWeight = computed(() => (plan.value?.items || []).reduce((sum, item) => sum + Number(item.grossWeightKg || 0), 0)) @@ -118,10 +118,12 @@ function preferredContainerId() { const candidateColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 92, fixed: 'left', slotName: 'actions' }, + { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, fixed: 'left', showOverflowTooltip: true }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 140 }, { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 100 }, + { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, { key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 135, slotName: 'unitGrossWeight' }, - { key: 'unitVolumeCbm', label: '单箱体积', width: 105, slotName: 'unitVolume' }, + { key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 145, slotName: 'averageUnitVolume' }, ] const itemColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 170, fixed: 'left', slotName: 'actions' }, @@ -139,7 +141,7 @@ function candidateRow(source: object) { return source as SmartLoadingCandidate } function itemRow(source: object) { return source as SmartLoadingItem } function adjustmentMax(source: object) { const item = itemRow(source) - const remaining = candidates.value.find((candidate) => candidate.inboundDetailId === item.inboundDetailId)?.availablePackageCount || 0 + const remaining = candidates.value.find((candidate) => candidate.inboundId === item.inboundId)?.availablePackageCount || 0 return Math.max(item.minPackageCount, item.packageCount + remaining) } function displayCabinetNo(container: ContainerBookingResource | null | undefined) { @@ -283,7 +285,6 @@ async function assign() { await mutate(async () => { acceptPlan(await assignSmartLoadingItem(plan.value!.id, { inboundId: selectedCandidate.value!.inboundId, - inboundDetailId: selectedCandidate.value!.inboundDetailId, packageCount: assignForm.packageCount, expectedVersion: plan.value!.version, })) @@ -439,12 +440,13 @@ onMounted(() => { void load(); void loadCustomers() }) size="small" type="primary" plain - :disabled="assignedDetailIds.has(row.inboundDetailId) || row.availablePackageCount < 1" + :disabled="assignedInboundIds.has(row.inboundId) || row.availablePackageCount < 1" @click="openAssign(row)" >加入 - + + @@ -531,7 +533,8 @@ onMounted(() => { void load(); void loadCustomers() })

{{ selectedCandidate.customerName || '客户待补充' }}

-

可用 {{ selectedCandidate.availablePackageCount }} 箱 · 平均单箱毛重 {{ formatNumber(Number(selectedCandidate.unitGrossWeightKg || 0), 1) }} kg · 单箱体积 {{ formatNumber(Number(selectedCandidate.unitVolumeCbm || 0)) }} m³

+

入仓码 {{ selectedCandidate.inboundCode || '—' }} · 可用 {{ selectedCandidate.availablePackageCount }} 箱 · 平均单箱毛重 {{ formatNumber(Number(selectedCandidate.unitGrossWeightKg || 0), 1) }} kg · 平均单箱体积 {{ formatNumber(Number(selectedCandidate.averageUnitVolumeCbm || 0)) }} m³

+ 本次只记录该入仓码的计划装柜箱数,具体箱号归属为未核验。
diff --git a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts index 3d9a2762..1f40f3ac 100644 --- a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts +++ b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts @@ -37,7 +37,7 @@ describe('SmartLoadingWorkbenchView', () => { vi.clearAllMocks() vi.mocked(getContainer).mockResolvedValue(container) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue(planResponse) - vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsName: '服装', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, unitVolumeCbm: 0.2 }]) + vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsSummary: '服装', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 2.4 }]) }) it('restores the real draft and explains candidate capacity', async () => { diff --git a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue index 6cc477f8..599ad0e8 100644 --- a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue +++ b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue @@ -41,7 +41,7 @@ const canPush = computed(() => access.can('platform:firstleg:smart-loading:push' const canViewLoadingList = computed(() => access.can('platform:firstleg:loading-list:list')) const editable = computed(() => plan.value?.canEdit === true) const assignable = computed(() => plan.value?.canAssign === true) -const assignedDetailIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundDetailId))) +const assignedInboundIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundId))) const totalVolume = computed(() => (plan.value?.items || []).reduce((sum, item) => sum + Number(item.volumeCbm || 0), 0)) const totalWeight = computed(() => (plan.value?.items || []).reduce((sum, item) => sum + Number(item.grossWeightKg || 0), 0)) const volumeLimit = computed(() => Number(container.value?.capacityCbm || 0) * 1.1) @@ -63,11 +63,12 @@ const itemColumns: DataTableColumn[] = [ ] const candidateColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 110, fixed: 'left', slotName: 'actions' }, - { key: 'order', label: '入仓码', minWidth: 190, slotName: 'order' }, + { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, fixed: 'left', showOverflowTooltip: true }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 150 }, - { key: 'goodsName', prop: 'goodsName', label: '货物', minWidth: 180 }, + { key: 'goodsSummary', prop: 'goodsSummary', label: '货物摘要', minWidth: 180, showOverflowTooltip: true }, { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 110 }, - { key: 'unitVolumeCbm', label: '单箱体积', width: 120, slotName: 'unitVolume' }, + { key: 'cartonIdentityStatus', label: '箱号归属', width: 110, slotName: 'cartonIdentityStatus' }, + { key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 145, slotName: 'averageUnitVolume' }, { key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 140, slotName: 'unitGrossWeight' }, ] @@ -77,7 +78,7 @@ function candidateRow(source: object) { return source as SmartLoadingCandidate } function itemRow(source: object) { return source as SmartLoadingItem } function explainCandidate(source: object) { const candidate = candidateRow(source) - const volume = Number(candidate.unitVolumeCbm || 0) + const volume = Number(candidate.averageUnitVolumeCbm || 0) const weight = Number(candidate.unitGrossWeightKg || 0) const volumeRoom = volumeLimit.value ? Math.floor(Math.max(volumeLimit.value - totalVolume.value, 0) / Math.max(volume, 0.000001)) : candidate.availablePackageCount const weightRoom = weightLimit.value ? Math.floor(Math.max(weightLimit.value - totalWeight.value, 0) / Math.max(weight, 0.000001)) : candidate.availablePackageCount @@ -139,7 +140,7 @@ async function assign() { mutating.value = true mutationError.value = null try { - acceptPlan(await assignSmartLoadingItem(plan.value.id, { inboundId: selectedCandidate.value.inboundId, inboundDetailId: selectedCandidate.value.inboundDetailId, packageCount: assignForm.packageCount, expectedVersion: plan.value.version })) + acceptPlan(await assignSmartLoadingItem(plan.value.id, { inboundId: selectedCandidate.value.inboundId, packageCount: assignForm.packageCount, expectedVersion: plan.value.version })) assignOpen.value = false; ElMessage.success('货物已分配') } catch (error) { await handleMutationError(error, '分配失败') } finally { mutating.value = false } @@ -249,14 +250,14 @@ onMounted(load) - - - + + + -

{{ selectedCandidate.goodsName || '未命名货物' }}

{{ selectedCandidate.inboundCode || '入仓码暂缺' }} · 可用 {{ selectedCandidate.availablePackageCount }} 箱

+

{{ selectedCandidate.inboundCode || '入仓码暂缺' }}

{{ selectedCandidate.goodsSummary || '货物摘要暂缺' }} · 可用 {{ selectedCandidate.availablePackageCount }} 箱

只记录数量,具体上柜箱号未核验。
diff --git a/src/modules/container-loading/types.ts b/src/modules/container-loading/types.ts index bef5174f..01a3dc2e 100644 --- a/src/modules/container-loading/types.ts +++ b/src/modules/container-loading/types.ts @@ -213,7 +213,6 @@ export interface ContainerSaveRequest { export interface SmartLoadingCandidate { accountId: string inboundId: string - inboundDetailId: string orderId: string orderNo: string | null inboundCode: string | null @@ -224,11 +223,12 @@ export interface SmartLoadingCandidate { destinationWarehouseCode: string | null destinationWarehouseName: string | null customerName: string | null - goodsName: string | null + goodsSummary: string | null availablePackageCount: number unitGrossWeightKg: number | null unitNetWeightKg: number | null - unitVolumeCbm: number | null + averageUnitVolumeCbm: number | null + availableVolumeCbm: number | null } export interface SmartLoadingItem {