diff --git a/src/modules/first-leg/utils/firstLegOrder.test.mjs b/src/modules/first-leg/utils/firstLegOrder.test.mjs
index d7d6a93..ec768bb 100644
--- a/src/modules/first-leg/utils/firstLegOrder.test.mjs
+++ b/src/modules/first-leg/utils/firstLegOrder.test.mjs
@@ -185,10 +185,15 @@ test('counts one physical carton series for SKU lines in the same mixed-carton g
const first = { ...validDraft().items[0], packingGroupNo: 'A', packageCount: 10, pieces: 20, unitsPerPackage: 2 }
const second = { ...first, id: 'cargo-2', sku: 'SKU-002', pieces: 30, unitsPerPackage: 3 }
- assert.equal(summarizeFirstLegCargo([first, second]).packageCount, 10)
+ assert.deepEqual(summarizeFirstLegCargo([first, second]), {
+ pieces: 50,
+ packageCount: 10,
+ grossWeightKg: first.grossWeightKg,
+ volumeCbm: first.volumeCbm,
+ })
const draft = { ...validDraft(), fulfillmentType: 'ONE_CLICK_DROPSHIP', items: [first, second] }
- assert.ok(!validateFirstLegOrderDraft(draft).some((message) => message.includes('混箱组合')))
+ assert.ok(!validateFirstLegOrderDraft(draft).some((message) => message.includes('同箱 SKU')))
second.packageCount = 9
- assert.ok(validateFirstLegOrderDraft(draft).includes('混箱组合 A 的箱数、包装类型和箱规必须一致'))
+ assert.ok(validateFirstLegOrderDraft(draft).includes('同箱 SKU 的箱数、包装类型、箱规、重量和体积必须一致'))
})
diff --git a/src/modules/first-leg/utils/firstLegOrder.ts b/src/modules/first-leg/utils/firstLegOrder.ts
index 708ce27..169679c 100644
--- a/src/modules/first-leg/utils/firstLegOrder.ts
+++ b/src/modules/first-leg/utils/firstLegOrder.ts
@@ -150,8 +150,10 @@ export function validateFirstLegOrderDraft(draft: FirstLegOrderDraft) {
const first = groups.get(group)
if (!first) groups.set(group, item)
else if (first.packageCount !== item.packageCount || first.packageType !== item.packageType
- || first.lengthCm !== item.lengthCm || first.widthCm !== item.widthCm || first.heightCm !== item.heightCm) {
- errors.push(`混箱组合 ${group} 的箱数、包装类型和箱规必须一致`)
+ || first.lengthCm !== item.lengthCm || first.widthCm !== item.widthCm || first.heightCm !== item.heightCm
+ || first.grossWeightKg !== item.grossWeightKg || first.netWeightKg !== item.netWeightKg
+ || first.volumeCbm !== item.volumeCbm) {
+ errors.push(`同箱 SKU 的箱数、包装类型、箱规、重量和体积必须一致`)
}
})
}
@@ -164,13 +166,14 @@ export function summarizeFirstLegCargo(items: FirstLegCargoItem[]) {
return items.reduce(
(summary, item) => {
const group = item.packingGroupNo.trim()
- const packageCount = group && countedGroups.has(group) ? 0 : item.packageCount
+ const alreadyCounted = Boolean(group && countedGroups.has(group))
+ const packageCount = alreadyCounted ? 0 : item.packageCount
if (group) countedGroups.add(group)
return ({
pieces: summary.pieces + item.pieces,
packageCount: summary.packageCount + packageCount,
- grossWeightKg: summary.grossWeightKg + item.grossWeightKg,
- volumeCbm: summary.volumeCbm + item.volumeCbm,
+ grossWeightKg: summary.grossWeightKg + (alreadyCounted ? 0 : item.grossWeightKg),
+ volumeCbm: summary.volumeCbm + (alreadyCounted ? 0 : item.volumeCbm),
})
},
{ pieces: 0, packageCount: 0, grossWeightKg: 0, volumeCbm: 0 },
diff --git a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs
index 93336b5..eea643e 100644
--- a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs
+++ b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs
@@ -357,6 +357,13 @@ test('distills order creation into the confirmed prototype sections with server
assert.doesNotMatch(source, /未填写行标识/)
assert.match(source, /batchImportError[\s\S]*requestId[\s\S]*traceId/)
assert.match(source, /添加行[\s\S]*删除所选行/)
+ assert.match(source, /添加 SKU/)
+ assert.match(source, /addSkuToPackingGroup/)
+ assert.match(source, /shared-carton-value">同箱/)
+ assert.match(source, /syncSkuQuantity/)
+ assert.match(source, /每个箱组默认装一个 SKU/)
+ assert.doesNotMatch(source, /v-model="item\.packingGroupNo"/)
+ assert.doesNotMatch(cargoTableSource, /
]*>混箱组合/)
assert.doesNotMatch(source, /货物明细导入模板\.xls/)
for (const heading of ['SKU NO.', '中文品名', '箱规长 cm', '总毛重', '报关 HS CODE', '货件编号', '跟踪编号', 'UPC NO.']) {
assert.ok(cargoTableSource.includes(heading))
diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue
index bbabecd..83de19d 100644
--- a/src/modules/first-leg/views/FirstLegOrdersView.vue
+++ b/src/modules/first-leg/views/FirstLegOrdersView.vue
@@ -215,6 +215,7 @@ const destinationWarehousesLoading = ref(false)
const pageDataLoading = computed(() => supportingDataLoading.value || (!isCreatePage.value && store.loading))
const editorBaseline = ref('')
let cargoSequence = 0
+let packingGroupSequence = 0
let inboundBillingRequestSequence = 0
let channelRequestSequence = 0
let estimateRequestSequence = 0
@@ -1332,6 +1333,71 @@ function addCargoItem() {
resetEditorValidation()
}
+const sharedPackingFields = [
+ 'packageType', 'packageCount', 'lengthCm', 'widthCm', 'heightCm',
+ 'grossWeightKg', 'netWeightKg', 'volumeCbm',
+] as const
+
+function packingGroupFor(item: FirstLegCargoItem) {
+ return item.packingGroupNo.trim()
+}
+
+function mixedSkuCount(item: FirstLegCargoItem) {
+ const group = packingGroupFor(item)
+ return group ? form.items.filter((candidate) => packingGroupFor(candidate) === group).length : 1
+}
+
+function isAdditionalMixedSku(item: FirstLegCargoItem, index: number) {
+ const group = packingGroupFor(item)
+ return Boolean(group && form.items.slice(0, index).some((candidate) => packingGroupFor(candidate) === group))
+}
+
+function syncSkuQuantity(item: FirstLegCargoItem) {
+ if (Number.isInteger(item.unitsPerPackage) && item.unitsPerPackage > 0
+ && Number.isInteger(item.packageCount) && item.packageCount > 0) {
+ item.pieces = item.unitsPerPackage * item.packageCount
+ }
+}
+
+function syncPackingGroupFields(source: FirstLegCargoItem) {
+ if (form.fulfillmentType !== 'ONE_CLICK_DROPSHIP') return
+ syncSkuQuantity(source)
+ const group = packingGroupFor(source)
+ if (!group) return
+ form.items.forEach((item) => {
+ if (item === source || packingGroupFor(item) !== group) return
+ sharedPackingFields.forEach((field) => { item[field] = source[field] as never })
+ syncSkuQuantity(item)
+ })
+}
+
+function normalizeSingleSkuGroups() {
+ const counts = new Map()
+ form.items.forEach((item) => {
+ const group = packingGroupFor(item)
+ if (group) counts.set(group, (counts.get(group) ?? 0) + 1)
+ })
+ form.items.forEach((item) => {
+ const group = packingGroupFor(item)
+ if (group && counts.get(group) === 1) item.packingGroupNo = ''
+ })
+}
+
+function addSkuToPackingGroup(index: number) {
+ const source = form.items[index]
+ if (!source) return
+ const group = packingGroupFor(source) || `PG-${Date.now().toString(36)}-${++packingGroupSequence}`
+ source.packingGroupNo = group
+ const added = createEmptyCargoItem(`new-cargo-${++cargoSequence}`)
+ added.packingGroupNo = group
+ sharedPackingFields.forEach((field) => { added[field] = source[field] as never })
+ syncSkuQuantity(added)
+ let insertAt = index + 1
+ while (insertAt < form.items.length && packingGroupFor(form.items[insertAt]!) === group) insertAt++
+ form.items.splice(insertAt, 0, added)
+ resetEditorValidation()
+}
+
function removeCargoItem(index: number) {
if (form.items.length === 1) {
ElMessage.warning('订单至少需要保留一条货物明细')
@@ -1339,6 +1405,7 @@ function removeCargoItem(index: number) {
}
const [removed] = form.items.splice(index, 1)
if (removed) releaseProductImagePreviews([removed])
+ normalizeSingleSkuGroups()
resetEditorValidation()
}
@@ -1503,6 +1570,7 @@ function removeSelectedCargoItems() {
const selectedIds = new Set(selectedCargoIds.value)
const removedItems = form.items.filter((item) => selectedIds.has(item.id))
form.items.splice(0, form.items.length, ...form.items.filter((item) => !selectedIds.has(item.id)))
+ normalizeSingleSkuGroups()
releaseProductImagePreviews(removedItems)
selectedCargoIds.value = []
resetEditorValidation()
@@ -1969,32 +2037,31 @@ async function searchOrders(model: QueryModel) {
可逐行维护货物明细。
-
+
| # |
- SKU NO.SKU必填 | 混箱组合同号为同一批箱 | 中文品名 | 图片 | 英文品名 | 报关 HS CODE | 清关 HS CODE | 包装类型 | 材质 | 危险品 | 木制品勾选后须商检 |
+ SKU NO.SKU必填 | 中文品名 | 图片 | 英文品名 | 报关 HS CODE | 清关 HS CODE | 包装类型 | 材质 | 危险品 | 木制品勾选后须商检 |
箱规长 cm | 箱规宽 cm | 箱规高 cm | 每箱数量 | 箱数 | *总毛重 | *总体积 | 总数量 | 总净重 |
清关申报单价 USD | 清关申报金额 USD | 用途 | 型号 | 品牌 | 产品规格 长×宽×高 | 货件编号货件编号必填 | 跟踪编号跟踪编号必填 | UPC NO. | 备注 | 操作 |
-
+
| {{ index + 1 }} |
- |
- |
+ 同箱 |
|
|
| | |
- | |
+ 同箱 | |
| 第 {{ index + 1 }} 条须商检 |
- | | |
- | | | | | |
+ 同上 | 同上 | 同上 |
+ | 同箱 | 同箱 | 同箱 | {{ item.pieces }} | 同箱 |
| | | | |
×× |
- | | | | 删除 |
+ | | | | 添加 SKU删除 |
- 相同混箱组合号表示相同一批物理箱;组内箱数、包装类型和箱规必须一致,物理箱数只计算一次。SKU NO.、货件编号、跟踪编号为 FBA 货物必填项,UPC NO. 选填
+ 每个箱组默认装一个 SKU;同一箱内有多个 SKU 时点击“添加 SKU”,箱数、箱规、重量和体积只填写一次。SKU NO.、货件编号、跟踪编号为 FBA 货物必填项,UPC NO. 选填
|