From dd7aa0e4cf05950edb35332803b769d8e7ca5af7 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 7 Aug 2026 18:15:59 +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 --- src/modules/first-leg/api/firstLeg.ts | 3 ++ .../first-leg/store/firstLegOrders.test.mjs | 2 + src/modules/first-leg/store/firstLegOrders.ts | 10 +++- src/modules/first-leg/types/firstLegOrder.ts | 5 ++ .../first-leg/utils/firstLegOrder.test.mjs | 18 +++++++- src/modules/first-leg/utils/firstLegOrder.ts | 46 ++++++++++++++++++- .../first-leg/views/FirstLegOrdersView.vue | 16 +++++-- 7 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/modules/first-leg/api/firstLeg.ts b/src/modules/first-leg/api/firstLeg.ts index e5a99fb..665193f 100644 --- a/src/modules/first-leg/api/firstLeg.ts +++ b/src/modules/first-leg/api/firstLeg.ts @@ -85,6 +85,9 @@ export interface FirstLegOrderCargoPayload { packageCount: number grossWeightKg: number volumeCbm: number + lengthCm?: number + widthCm?: number + heightCm?: number declaredValue?: number currency?: string dangerousGoods?: boolean diff --git a/src/modules/first-leg/store/firstLegOrders.test.mjs b/src/modules/first-leg/store/firstLegOrders.test.mjs index 1cdb121..07e1891 100644 --- a/src/modules/first-leg/store/firstLegOrders.test.mjs +++ b/src/modules/first-leg/store/firstLegOrders.test.mjs @@ -88,6 +88,8 @@ test('sends backend-required channel, pricing, customs, destination and cargo fi assert.match(storeSource, /destinationWarehouseId: draft\.destinationType === 'PRIVATE' \? undefined : draft\.destinationWarehouseId/) assert.match(storeSource, /draft\.pickupMethod === 'PICKUP'.*draft\.shipperPartySnapshot/s) assert.match(storeSource, /draft\.destinationType === 'PRIVATE'.*draft\.consigneePartySnapshot/s) + for (const dimension of ['lengthCm', 'widthCm', 'heightCm']) assert.match(storeSource, new RegExp(`${dimension}: item\\.${dimension}`)) + assert.match(storeSource, /inferCargoBoxSizeType\(item\.lengthCm, item\.widthCm, item\.heightCm\)/) }) test('separates domestic receiving warehouses from public destinations', () => { diff --git a/src/modules/first-leg/store/firstLegOrders.ts b/src/modules/first-leg/store/firstLegOrders.ts index 934e04a..4cd0cee 100644 --- a/src/modules/first-leg/store/firstLegOrders.ts +++ b/src/modules/first-leg/store/firstLegOrders.ts @@ -27,7 +27,7 @@ import type { MerchantTenantStatus } from '@/modules/tenant-management/public' import type { PlatformReceivingWarehouse, PlatformReceivingWarehouseSnapshot } from '@chongchong/shared' import type { FirstLegPartySnapshot } from '@/modules/first-leg/data/firstLegParties' import type { FirstLegOrder, FirstLegOrderDraft, FirstLegOrderTimelineItem } from '@/modules/first-leg/types/firstLegOrder' -import { canClientEditFirstLegOrder, getFirstLegStatusLabel } from '@/modules/first-leg/utils/firstLegOrder' +import { canClientEditFirstLegOrder, getFirstLegStatusLabel, inferCargoBoxSizeType } from '@/modules/first-leg/utils/firstLegOrder' import { loadAllWarehouseRecords } from '@/modules/first-leg/utils/warehousePagination' export type ClientWarehouseType = 'DOMESTIC' | 'FBA' | 'OVERSEAS' @@ -111,6 +111,9 @@ function savePayload(draft: FirstLegOrderDraft, warehouse: ClientWarehouse, cust packageCount: item.packageCount, grossWeightKg: item.grossWeightKg, volumeCbm: item.volumeCbm, + lengthCm: item.lengthCm || undefined, + widthCm: item.widthCm || undefined, + heightCm: item.heightCm || undefined, declaredValue: item.declaredValue || undefined, currency: item.currency || undefined, dangerousGoods: item.dangerousGoods, @@ -273,7 +276,10 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar items: (detail?.cargoes ?? []).map((item) => ({ id: String(item.id), sku: item.skuNo ?? '', upc: item.upcNo ?? '', cargoNameCn: item.cargoName, cargoNameEn: item.cargoNameEn ?? '', pieces: item.totalQuantity ?? item.packageCount, packageCount: item.packageCount, grossWeightKg: item.grossWeightKg, - volumeCbm: item.volumeCbm, containerType: '', containerCount: 0, remark: item.cargoRemark ?? '', + volumeCbm: item.volumeCbm, + boxSizeType: inferCargoBoxSizeType(item.lengthCm, item.widthCm, item.heightCm), + lengthCm: item.lengthCm ?? 0, widthCm: item.widthCm ?? 0, heightCm: item.heightCm ?? 0, + containerType: '', containerCount: 0, remark: item.cargoRemark ?? '', packageType: item.packageType, hsCode: item.hsCode ?? '', declaredValue: item.declaredValue ?? 0, currency: item.currency ?? 'USD', dangerousGoods: item.dangerousGoods ?? false, })), diff --git a/src/modules/first-leg/types/firstLegOrder.ts b/src/modules/first-leg/types/firstLegOrder.ts index 2ea2743..ad41634 100644 --- a/src/modules/first-leg/types/firstLegOrder.ts +++ b/src/modules/first-leg/types/firstLegOrder.ts @@ -7,6 +7,7 @@ import type { FirstLegPartySnapshot } from '../data/firstLegParties.ts' export type FirstLegBusinessType = 'EXPORT' | 'IMPORT' export type FirstLegPriority = 'NORMAL' | 'URGENT' | 'EXPEDITED' +export type CargoBoxSizeType = '' | 'SMALL' | 'MEDIUM' | 'LARGE' | 'EXTRA_LARGE' export interface FirstLegCargoItem { id: string @@ -18,6 +19,10 @@ export interface FirstLegCargoItem { packageCount: number grossWeightKg: number volumeCbm: number + boxSizeType: CargoBoxSizeType + lengthCm: number + widthCm: number + heightCm: number containerType: string containerCount: number remark: string diff --git a/src/modules/first-leg/utils/firstLegOrder.test.mjs b/src/modules/first-leg/utils/firstLegOrder.test.mjs index 8187b03..089c7ba 100644 --- a/src/modules/first-leg/utils/firstLegOrder.test.mjs +++ b/src/modules/first-leg/utils/firstLegOrder.test.mjs @@ -2,12 +2,14 @@ import assert from 'node:assert/strict' import test from 'node:test' import { createFirstLegPartySnapshot } from '../data/firstLegParties.ts' import { + applyCargoBoxSize, canClientCancelFirstLegOrder, canClientEditFirstLegOrder, createEmptyCargoItem, getFirstLegStatusLabel, getFirstLegTransportModeLabel, summarizeFirstLegCargo, + updateCargoBoxVolume, validateFirstLegOrderDraft, } from './firstLegOrder.ts' @@ -52,10 +54,24 @@ function validDraft() { destinationType: 'FBA', amazonWarehouseCode: 'ONT8', shipmentId: 'FBA-001', - items: [{ ...createEmptyCargoItem('cargo-1'), sku: 'SKU-001', upc: '012345678905', cargoNameCn: '家居用品', pieces: 100, grossWeightKg: 1200, volumeCbm: 3.5, containerType: '40HQ', containerCount: 1 }], + items: [{ ...createEmptyCargoItem('cargo-1'), sku: 'SKU-001', upc: '012345678905', cargoNameCn: '家居用品', pieces: 100, grossWeightKg: 1200, volumeCbm: 3.5, boxSizeType: 'SMALL', lengthCm: 34, widthCm: 24, heightCm: 24, containerType: '40HQ', containerCount: 1 }], } } +test('applies the four fixed box sizes and recalculates total CBM from package count', () => { + const item = createEmptyCargoItem('cargo-box') + item.packageCount = 2 + applyCargoBoxSize(item, 'SMALL') + assert.deepEqual([item.lengthCm, item.widthCm, item.heightCm, item.volumeCbm], [34, 24, 24, 0.039]) + + item.packageCount = 3 + updateCargoBoxVolume(item) + assert.equal(item.volumeCbm, 0.059) + + applyCargoBoxSize(item, '') + assert.deepEqual([item.lengthCm, item.widthCm, item.heightCm, item.volumeCbm], [0, 0, 0, 0]) +}) + test('uses the confirmed status and transport labels', () => { assert.equal(getFirstLegStatusLabel('WAIT_MEASURE'), '待测量数据') assert.equal(getFirstLegStatusLabel('INBOUNDED'), '已入库') diff --git a/src/modules/first-leg/utils/firstLegOrder.ts b/src/modules/first-leg/utils/firstLegOrder.ts index f68849b..d0db790 100644 --- a/src/modules/first-leg/utils/firstLegOrder.ts +++ b/src/modules/first-leg/utils/firstLegOrder.ts @@ -4,7 +4,46 @@ import { type FirstLegOrderStatus, type FirstLegTransportMode, } from '@chongchong/shared' -import type { FirstLegCargoItem, FirstLegOrderDraft } from '../types/firstLegOrder.ts' +import type { CargoBoxSizeType, FirstLegCargoItem, FirstLegOrderDraft } from '../types/firstLegOrder.ts' + +export const CARGO_BOX_SIZES = [ + { code: 'SMALL', label: '小箱', inner: '30 × 20 × 20', lengthCm: 34, widthCm: 24, heightCm: 24 }, + { code: 'MEDIUM', label: '中箱', inner: '40 × 30 × 30', lengthCm: 44, widthCm: 34, heightCm: 34 }, + { code: 'LARGE', label: '大箱', inner: '50 × 40 × 40', lengthCm: 54, widthCm: 44, heightCm: 44 }, + { code: 'EXTRA_LARGE', label: '特大箱', inner: '60 × 50 × 50', lengthCm: 64, widthCm: 54, heightCm: 54 }, +] as const satisfies ReadonlyArray<{ + code: Exclude + label: string + inner: string + lengthCm: number + widthCm: number + heightCm: number +}> + +export function inferCargoBoxSizeType(lengthCm?: number, widthCm?: number, heightCm?: number): CargoBoxSizeType { + return CARGO_BOX_SIZES.find((box) => box.lengthCm === lengthCm && box.widthCm === widthCm && box.heightCm === heightCm)?.code ?? '' +} + +export function applyCargoBoxSize(item: FirstLegCargoItem, type: CargoBoxSizeType) { + item.boxSizeType = type + const box = CARGO_BOX_SIZES.find((option) => option.code === type) + if (!box) { + item.lengthCm = 0 + item.widthCm = 0 + item.heightCm = 0 + item.volumeCbm = 0 + return + } + item.lengthCm = box.lengthCm + item.widthCm = box.widthCm + item.heightCm = box.heightCm + updateCargoBoxVolume(item) +} + +export function updateCargoBoxVolume(item: FirstLegCargoItem) { + if (!item.boxSizeType || item.packageCount <= 0) return + item.volumeCbm = Number(((item.lengthCm * item.widthCm * item.heightCm * item.packageCount) / 1_000_000).toFixed(3)) +} const statusLabels = new Map( FIRST_LEG_ORDER_STATUSES.map((item) => [item.code, item.label]), @@ -57,6 +96,10 @@ export function createEmptyCargoItem(id = `cargo-${Date.now()}`): FirstLegCargoI packageCount: 1, grossWeightKg: 0, volumeCbm: 0, + boxSizeType: '', + lengthCm: 0, + widthCm: 0, + heightCm: 0, containerType: '', containerCount: 0, remark: '', @@ -104,6 +147,7 @@ export function validateFirstLegOrderDraft(draft: FirstLegOrderDraft) { const line = `第 ${index + 1} 条货物` if (!item.cargoNameCn.trim()) errors.push(`${line}缺少中文品名`) if (!item.packageType.trim()) errors.push(`${line}缺少包装类型`) + if (!item.boxSizeType) errors.push(`${line}请选择箱规`) if (draft.destinationType === 'FBA' && !item.sku.trim()) errors.push(`${line}缺少客户 SKU`) if (draft.destinationType === 'FBA' && !item.upc.trim()) errors.push(`${line}缺少 UPC`) if (!Number.isInteger(item.pieces) || item.pieces <= 0) errors.push(`${line}件数必须为正整数`) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue index acf4bc6..1e0bb75 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.vue +++ b/src/modules/first-leg/views/FirstLegOrdersView.vue @@ -23,15 +23,18 @@ import { useTenantAdministrationStore } from '@/modules/tenant-management/public import { useAuthStore } from '@/modules/auth/public' import { useUnsavedWorkRegistration } from '@/navigation/workspace-tabs/unsavedWork' import type { FirstLegPartyRole, FirstLegPartySnapshot } from '@/modules/first-leg/data/firstLegParties' -import type { FirstLegOrder, FirstLegOrderDraft } from '@/modules/first-leg/types/firstLegOrder' +import type { CargoBoxSizeType, FirstLegCargoItem, FirstLegOrder, FirstLegOrderDraft } from '@/modules/first-leg/types/firstLegOrder' import type { MerchantInboundBilling } from '@/modules/first-leg/types/inboundBilling' import { canClientEditFirstLegOrder, + applyCargoBoxSize, + CARGO_BOX_SIZES, createEmptyCargoItem, getFirstLegStatusLabel, getFirstLegStatusTone, getFirstLegTransportModeLabel, summarizeFirstLegCargo, + updateCargoBoxVolume, validateFirstLegOrderDraft, } from '@/modules/first-leg/utils/firstLegOrder' @@ -870,6 +873,10 @@ function addCargoItem() { resetEditorValidation() } +function changeCargoBoxSize(item: FirstLegCargoItem, type: CargoBoxSizeType) { + applyCargoBoxSize(item, type) +} + function removeCargoItem(index: number) { if (form.items.length === 1) { ElMessage.warning('订单至少需要保留一条货物明细') @@ -1125,16 +1132,17 @@ async function searchOrders(model: QueryModel) { - + + - +
- +