diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index 47ee4ad2..5ab09bd2 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -101,6 +101,8 @@ describe('GlobalSmartLoadingView', () => { expect(wrapper.text()).toContain('IN-2') expect(wrapper.text()).not.toContain('ORD-1') expect(wrapper.text()).not.toContain('ORD-2') + expect(wrapper.get('.candidate-pane').text()).toContain('120 kg') + expect(wrapper.get('.candidate-pane').text()).toContain('2.4 m³') }) it('uses the top-level container channel snapshot without expecting a nested snapshot', async () => { diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue index 53c4a5a7..ac2a5b26 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue @@ -88,7 +88,7 @@ const editable = computed(() => plan.value?.canEdit === true) const assignable = computed(() => plan.value?.canAssign === true) const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null) const assignedInboundIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundId))) -type SmartLoadingCandidateRow = SmartLoadingCandidate & { sourceCandidates: SmartLoadingCandidate[]; memberInboundCodes: string[]; memberOrderSummaries: string[]; memberOrderCount: number; inboundFragmentCount: number } +type SmartLoadingCandidateRow = SmartLoadingCandidate & { sourceCandidates: SmartLoadingCandidate[]; memberInboundCodes: string[]; memberOrderSummaries: string[]; memberOrderCount: number; inboundFragmentCount: number; totalGrossWeightKg: number; totalVolumeCbm: number } function memberOrderSummaries(members: T[]) { const orders = new Map() for (const member of members) { @@ -104,12 +104,20 @@ function groupMemberLabel(group: SmartLoadingPlanCoLoadGroup) { ? `${group.memberOrderCount} 票` : `${group.memberOrderCount} 票 · ${group.inboundFragmentCount} 个入仓片段` } +function candidateTotalGrossWeight(candidate: SmartLoadingCandidate) { + return Number(candidate.unitGrossWeightKg || 0) * Number(candidate.availablePackageCount || 0) +} +function candidateTotalVolume(candidate: SmartLoadingCandidate) { + return candidate.availableVolumeCbm == null + ? Number(candidate.averageUnitVolumeCbm || 0) * Number(candidate.availablePackageCount || 0) + : Number(candidate.availableVolumeCbm) +} const displayedCandidates = computed(() => { const rows: SmartLoadingCandidateRow[] = [] const groups = new Map() for (const candidate of candidates.value) { if (!candidate.coLoadGroupId) { - rows.push({ ...candidate, sourceCandidates: [candidate], memberInboundCodes: [candidate.inboundCode || '入仓码暂缺'], memberOrderSummaries: memberOrderSummaries([candidate]), memberOrderCount: 1, inboundFragmentCount: 1 }) + rows.push({ ...candidate, sourceCandidates: [candidate], memberInboundCodes: [candidate.inboundCode || '入仓码暂缺'], memberOrderSummaries: memberOrderSummaries([candidate]), memberOrderCount: 1, inboundFragmentCount: 1, totalGrossWeightKg: candidateTotalGrossWeight(candidate), totalVolumeCbm: candidateTotalVolume(candidate) }) continue } groups.set(candidate.coLoadGroupId, [...(groups.get(candidate.coLoadGroupId) || []), candidate]) @@ -128,6 +136,8 @@ const displayedCandidates = computed(() => { memberOrderSummaries: memberOrderSummaries(members), memberOrderCount: new Set(members.map((member) => member.orderId)).size, inboundFragmentCount: Number(first.memberCount || members.length), + totalGrossWeightKg: members.reduce((sum, member) => sum + candidateTotalGrossWeight(member), 0), + totalVolumeCbm: members.reduce((sum, member) => sum + candidateTotalVolume(member), 0), }) } return rows @@ -241,13 +251,15 @@ 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: 'coLoadGroup', label: '同柜不可拆', minWidth: 210, slotName: 'coLoadGroup' }, - { 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: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 145, slotName: 'averageUnitVolume' }, + { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true }, + { key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' }, + { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 }, + { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 }, + // { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, + { key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 100, slotName: 'unitGrossWeight' }, + { key: 'totalGrossWeightKg', label: '总重量', width: 90, slotName: 'candidateTotalWeight' }, + { key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 100, slotName: 'averageUnitVolume' }, + { key: 'totalVolumeCbm', label: '总体积', width: 90, slotName: 'candidateTotalVolume' }, ] const itemColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 170, fixed: 'left', slotName: 'actions' }, @@ -627,7 +639,9 @@ onMounted(() => { void load(); void loadCustomers() }) + + diff --git a/src/modules/unified-orders/components/OrderDeclarationEditorDialog.test.ts b/src/modules/unified-orders/components/OrderDeclarationEditorDialog.test.ts index 2672bc63..3dd0c8ba 100644 --- a/src/modules/unified-orders/components/OrderDeclarationEditorDialog.test.ts +++ b/src/modules/unified-orders/components/OrderDeclarationEditorDialog.test.ts @@ -17,7 +17,7 @@ const detail = { }, order: { order: { version: 7, receivingWarehouseId: '700000000000000001', channelCode: 'OLD', channelName: '原渠道', customerRemark: '原备注' }, - cargoes: [{ itemNo: 1, cargoName: '台灯', packageCount: 2, totalQuantity: 20, lengthCm: 40, widthCm: 30, heightCm: 20, grossWeightKg: 18, volumeCbm: 0.048, currency: 'USD', material: '铝', usage: '照明' }], + cargoes: [{ itemNo: 1, cargoName: '台灯', packageCount: 2, totalQuantity: 20, lengthCm: 40, widthCm: 30, heightCm: 20, grossWeightKg: 18, volumeCbm: 0.048, currency: 'USD', material: '铝', productUsage: '照明' }], parties: [], internalNotes: [], statusHistory: [], }, timeline: [], @@ -68,7 +68,13 @@ describe('OrderDeclarationEditorDialog', () => { expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('原箱码继续有效,无需更换已打印箱唛'), '确认变更渠道', expect.any(Object)) expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('纸面上的旧渠道文字不会自动变化'), '确认变更渠道', expect.any(Object)) - expect(updatePlatformUnifiedOrderDeclaration).toHaveBeenCalledWith('208', '90001', expect.objectContaining({ expectedVersion: 7, estimatedVolumeCbm: 0.625, channelCode: 'NEW', channelName: '新渠道' })) + expect(updatePlatformUnifiedOrderDeclaration).toHaveBeenCalledWith('208', '90001', expect.objectContaining({ + expectedVersion: 7, + estimatedVolumeCbm: 0.625, + channelCode: 'NEW', + channelName: '新渠道', + cargoes: [expect.objectContaining({ itemNo: 1, usage: '照明' })], + })) expect(ElMessage.success).toHaveBeenCalledWith(expect.stringContaining('订单已标记为“曾变更渠道”')) expect(wrapper.emitted('updated')).toHaveLength(1) expect(wrapper.emitted('update:modelValue')).toContainEqual([false]) @@ -83,11 +89,13 @@ describe('OrderDeclarationEditorDialog', () => { expect(wrapper.find('.cargo-table').exists()).toBe(true) expect(wrapper.text()).toContain('箱数 / 数量') expect(wrapper.text()).toContain('材质 / 用途') + expect(wrapper.text()).toContain('铝 / 照明') expect(wrapper.text()).toContain('2 箱 / 20') const editButton = wrapper.findAll('button').find((button) => button.text() === '编辑')! await editButton.trigger('click') expect(wrapper.find('input[aria-label="币种"]').attributes('readonly')).toBeDefined() + expect(wrapper.findAllComponents({ name: 'ElInput' }).some((input) => input.props('modelValue') === '照明')).toBe(true) }) it('preserves the editor when the versioned update fails', async () => { diff --git a/src/modules/unified-orders/components/OrderDeclarationEditorDialog.vue b/src/modules/unified-orders/components/OrderDeclarationEditorDialog.vue index d4965fb3..8f7b0fe9 100644 --- a/src/modules/unified-orders/components/OrderDeclarationEditorDialog.vue +++ b/src/modules/unified-orders/components/OrderDeclarationEditorDialog.vue @@ -88,7 +88,7 @@ function resetForm() { hsCode: text(row, 'hsCode'), customsHsCode: text(row, 'customsHsCode'), unitsPerPackage: optionalNumber(row, 'unitsPerPackage'), unitDeclaredValue: optionalNumber(row, 'unitDeclaredValue'), declaredValue: optionalNumber(row, 'declaredValue'), - currency: text(row, 'currency'), material: text(row, 'material'), usage: text(row, 'usage'), + currency: text(row, 'currency'), material: text(row, 'material'), usage: text(row, 'productUsage'), brand: text(row, 'brand'), cargoRemark: text(row, 'cargoRemark') || text(row, 'remark'), } })