diff --git a/src/modules/first-leg/utils/firstLegOrder.test.mjs b/src/modules/first-leg/utils/firstLegOrder.test.mjs index c412805..5a22a74 100644 --- a/src/modules/first-leg/utils/firstLegOrder.test.mjs +++ b/src/modules/first-leg/utils/firstLegOrder.test.mjs @@ -162,10 +162,11 @@ test('requires public destination ids while keeping private addresses detached f test('requires customs inspection when any cargo is a wooden product', () => { const draft = validDraft() draft.items[0].woodenProduct = true + draft.items.push({ ...draft.items[0], id: 'cargo-2' }) draft.customsInspection = null - assert.ok(validateFirstLegOrderDraft(draft).includes('第 1 条货物为木制品,“是否商检”必须选择“是”')) + assert.ok(validateFirstLegOrderDraft(draft).includes('第 1、2 条货物为木制品,“是否商检”必须选择“是”')) draft.customsInspection = false - assert.ok(validateFirstLegOrderDraft(draft).includes('第 1 条货物为木制品,“是否商检”必须选择“是”')) + assert.ok(validateFirstLegOrderDraft(draft).includes('第 1、2 条货物为木制品,“是否商检”必须选择“是”')) draft.customsInspection = true assert.ok(!validateFirstLegOrderDraft(draft).some((message) => message.includes('为木制品'))) }) diff --git a/src/modules/first-leg/utils/firstLegOrder.ts b/src/modules/first-leg/utils/firstLegOrder.ts index 129667f..9305c90 100644 --- a/src/modules/first-leg/utils/firstLegOrder.ts +++ b/src/modules/first-leg/utils/firstLegOrder.ts @@ -141,9 +141,15 @@ export function validateFirstLegOrderDraft(draft: FirstLegOrderDraft) { } if (item.grossWeightKg <= 0) errors.push(`${line}毛重必须大于 0`) if (item.volumeCbm <= 0) errors.push(`${line}体积必须大于 0`) - if (item.woodenProduct && draft.customsInspection !== true) errors.push(`${line}为木制品,“是否商检”必须选择“是”`) }) + const woodenCargoLines = draft.items + .map((item, index) => item.woodenProduct ? index + 1 : null) + .filter((index): index is number => index != null) + if (woodenCargoLines.length && draft.customsInspection !== true) { + errors.push(`第 ${woodenCargoLines.join('、')} 条货物为木制品,“是否商检”必须选择“是”`) + } + if (draft.fulfillmentType === 'ONE_CLICK_DROPSHIP') { const groups = new Map() draft.items.forEach((item) => { diff --git a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs index cd9d91a..46e4b2d 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs +++ b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs @@ -299,6 +299,13 @@ test('selects platform destinations and uses customer-scoped server address book assert.match(source, /createMerchantOrderAddress\(payload\)/) assert.match(source, /updateMerchantOrderAddress\(form\.(?:senderAddressId|privateDestinationAddressId), payload\)/) assert.match(source, /disableMerchantOrderAddress\(id\)/) + assert.match(source, /function findAddressBookEntryId\(addressType: CustomerOrderAddress\['addressType'\], snapshot: FirstLegPartySnapshot \| null\)/) + assert.match(source, /senderAddressId: findAddressBookEntryId\('PICKUP', order\.shipperPartySnapshot\)/) + assert.match(source, /privateDestinationAddressId: findAddressBookEntryId\('PRIVATE_DESTINATION', order\.consigneePartySnapshot\)/) + assert.match(source, /function selectPickupAddress\(id: string \| null \| undefined\)[\s\S]*?form\.shipperPartySnapshot = createEmptyPartySnapshot\('SHIPPER'\)/) + assert.match(source, /function selectPrivateAddress\(id: string \| null \| undefined\)[\s\S]*?form\.consigneePartySnapshot = createEmptyPartySnapshot\('CONSIGNEE'\)/) + assert.match(source, /const existingAddressId = findAddressBookEntryId\('PICKUP', snapshot\)[\s\S]*?相同的企业提货地址已存在,已为您选中/) + assert.match(source, /const existingAddressId = findAddressBookEntryId\('PRIVATE_DESTINATION', snapshot\)[\s\S]*?相同的企业私人仓地址已存在,已为您选中/) assert.match(source, /v-model="form\.senderAddressId"/) assert.match(source, /v-model="form\.privateDestinationAddressId"/) assert.doesNotMatch(source, /localStorage|cc:first-leg:private-addresses/) @@ -334,7 +341,7 @@ test('collects the backend-required first-leg fields', () => { } assert.match(source, /form\.channelName = channel\?\.channelName/) assert.match(source, /v-model="item\.woodenProduct"/) - assert.match(source, /第 \{\{ woodenCargoLines\.join\('、'\) \}\} 条货物已勾选木制品/) + assert.match(source, /第 \{\{ woodenCargoLines\.join\('、'\) \}\} 条货物为木制品,请将“是否商检”选择为“是”/) assert.match(source, /第 \{\{ index \+ 1 \}\} 条须商检/) }) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue index 808c2e8..438f1f9 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.vue +++ b/src/modules/first-leg/views/FirstLegOrdersView.vue @@ -756,6 +756,25 @@ function removeCargoProductImage(item: FirstLegCargoItem) { item.productImageError = '' } +function normalizedAddressValue(value: string | null | undefined) { + return value?.trim().toLocaleLowerCase() ?? '' +} + +function findAddressBookEntryId(addressType: CustomerOrderAddress['addressType'], snapshot: FirstLegPartySnapshot | null) { + if (!snapshot) return '' + const address = orderAddresses.value.find((item) => item.addressType === addressType + && normalizedAddressValue(item.companyName || item.addressName) === normalizedAddressValue(snapshot.name) + && normalizedAddressValue(item.contactName) === normalizedAddressValue(snapshot.contactName) + && normalizedAddressValue(item.phone) === normalizedAddressValue(snapshot.phone) + && normalizedAddressValue(item.email) === normalizedAddressValue(snapshot.email) + && normalizedAddressValue(item.countryCode) === normalizedAddressValue(snapshot.countryRegion) + && normalizedAddressValue(item.province) === normalizedAddressValue(snapshot.province) + && normalizedAddressValue(item.city) === normalizedAddressValue(snapshot.city) + && normalizedAddressValue(item.postalCode) === normalizedAddressValue(snapshot.postalCode) + && normalizedAddressValue(item.addressLine) === normalizedAddressValue(snapshot.address)) + return address == null ? '' : String(address.id) +} + function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft { return { inboundCode: order.businessSummary.inboundCode, @@ -769,11 +788,11 @@ function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft { receivingWarehouseId: order.receivingWarehouseId, expectedDepartureDate: order.expectedDepartureDate, shipperPartyId: order.shipperPartyId, - senderAddressId: '', + senderAddressId: findAddressBookEntryId('PICKUP', order.shipperPartySnapshot), shipperPartySnapshot: order.shipperPartySnapshot ? { ...order.shipperPartySnapshot } : null, shipper: order.shipper, consigneePartyId: order.consigneePartyId, - privateDestinationAddressId: '', + privateDestinationAddressId: findAddressBookEntryId('PRIVATE_DESTINATION', order.consigneePartySnapshot), consigneePartySnapshot: order.consigneePartySnapshot ? { ...order.consigneePartySnapshot } : null, consignee: order.consignee, priority: order.priority, @@ -1000,9 +1019,19 @@ function syncPrivateDestinationName(value: string) { if (form.consigneePartySnapshot) form.consigneePartySnapshot.name = value } -function selectPrivateAddress(id: string) { +function selectPrivateAddress(id: string | null | undefined) { const address = privateAddresses.value.find((item) => String(item.id) === id) - if (!address) return + if (!address) { + form.privateDestinationAddressId = '' + form.consigneePartyId = '' + form.consigneePartySnapshot = createEmptyPartySnapshot('CONSIGNEE') + form.consignee = '' + form.destination = '' + form.destinationCountryCode = '' + form.destinationWarehouseId = '' + form.destinationWarehouseName = '' + return + } form.privateDestinationAddressId = String(address.id) form.consigneePartyId = '' form.consigneePartySnapshot = addressSnapshot(address, 'CONSIGNEE') @@ -1039,9 +1068,16 @@ async function loadOrderAddresses() { } } -function selectPickupAddress(id: string) { +function selectPickupAddress(id: string | null | undefined) { const address = pickupAddresses.value.find((item) => String(item.id) === id) - if (!address) return + if (!address) { + form.senderAddressId = '' + form.shipperPartyId = '' + form.shipperPartySnapshot = createEmptyPartySnapshot('SHIPPER') + form.shipper = '' + form.origin = '' + return + } form.senderAddressId = String(address.id) form.shipperPartyId = '' form.shipperPartySnapshot = addressSnapshot(address, 'SHIPPER') @@ -1079,6 +1115,12 @@ async function savePickupAddress() { ElMessage.warning('请先完整填写提货地址和联系人资料') return } + const existingAddressId = findAddressBookEntryId('PICKUP', snapshot) + if (existingAddressId && existingAddressId !== form.senderAddressId) { + selectPickupAddress(existingAddressId) + ElMessage.info('相同的企业提货地址已存在,已为您选中') + return + } try { const payload = addressPayload('PICKUP', snapshot!, addressName) if (form.senderAddressId) await updateMerchantOrderAddress(form.senderAddressId, payload) @@ -1100,6 +1142,12 @@ async function savePrivateAddress() { return } snapshot!.name = addressName + const existingAddressId = findAddressBookEntryId('PRIVATE_DESTINATION', snapshot) + if (existingAddressId && existingAddressId !== form.privateDestinationAddressId) { + selectPrivateAddress(existingAddressId) + ElMessage.info('相同的企业私人仓地址已存在,已为您选中') + return + } try { const payload = addressPayload('PRIVATE_DESTINATION', snapshot!, addressName) if (form.privateDestinationAddressId) await updateMerchantOrderAddress(form.privateDestinationAddressId, payload) @@ -1117,8 +1165,8 @@ async function removeOrderAddress(type: CustomerOrderAddress['addressType']) { if (!id) return try { await disableMerchantOrderAddress(id) - if (type === 'PICKUP') form.senderAddressId = '' - else form.privateDestinationAddressId = '' + if (type === 'PICKUP') selectPickupAddress(null) + else selectPrivateAddress(null) await loadOrderAddresses() ElMessage.success('地址已从企业地址簿停用') } catch (error) { @@ -2057,7 +2105,7 @@ async function searchOrders(model: QueryModel) { 买单退税 整柜散货 - 第 {{ woodenCargoLines.join('、') }} 条货物已勾选木制品,提交前“是否商检”必须选择“是”。 + 第 {{ woodenCargoLines.join('、') }} 条货物为木制品,请将“是否商检”选择为“是”。