diff --git a/shared/src/index.ts b/shared/src/index.ts index ceb64c2..42c4559 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -86,6 +86,7 @@ export const FIRST_LEG_ORDER_STATUSES = [ { code: 'DRAFT', label: '草稿' }, { code: 'PENDING_REVIEW', label: '待审核' }, { code: 'WAIT_MEASURE', label: '待测量数据' }, + { code: 'WAIT_PALLETIZE', label: '待组托' }, { code: 'WAIT_SPLIT', label: '待分柜' }, { code: 'LOADING', label: '装柜中' }, { code: 'LOADED', label: '装柜完成' }, diff --git a/src/modules/first-leg/api/firstLeg.ts b/src/modules/first-leg/api/firstLeg.ts index 8cbfa7c..5273066 100644 --- a/src/modules/first-leg/api/firstLeg.ts +++ b/src/modules/first-leg/api/firstLeg.ts @@ -217,6 +217,7 @@ export interface MerchantFirstLegOrder { customerId: number | string | null customerName: string fulfillmentType?: 'STANDARD' | 'ONE_CLICK_DROPSHIP' | null + lxCode?: string | null status: ApiFirstLegStatus transportMode?: ApiTransportMode tradeTerm: string | null diff --git a/src/modules/first-leg/api/inboundBilling.test.mjs b/src/modules/first-leg/api/inboundBilling.test.mjs index d0742c1..c238a74 100644 --- a/src/modules/first-leg/api/inboundBilling.test.mjs +++ b/src/modules/first-leg/api/inboundBilling.test.mjs @@ -35,3 +35,7 @@ test('keeps all backend Long identifiers as strings', () => { assert.doesNotMatch(typesSource, /(?:id|Id): number/) assert.doesNotMatch(typesSource, /internalNotes|statusHistory|files/) }) + +test('accepts the one-click dropship palletization process status', () => { + assert.match(typesSource, /MerchantInboundProcessStatus[\s\S]*?'WAIT_PALLETIZE'[\s\S]*?'WAIT_SPLIT'/) +}) diff --git a/src/modules/first-leg/api/unifiedOrders.test.mjs b/src/modules/first-leg/api/unifiedOrders.test.mjs index ca874ef..9e1ee43 100644 --- a/src/modules/first-leg/api/unifiedOrders.test.mjs +++ b/src/modules/first-leg/api/unifiedOrders.test.mjs @@ -4,8 +4,11 @@ import test from 'node:test' const source = readFileSync(new URL('./unifiedOrders.ts', import.meta.url), 'utf8') -test('keeps the merchant unified-order boundary and phase-two WAIT_SPLIT status', () => { +test('keeps the merchant unified-order boundary and one-click palletization status', () => { assert.match(source, /const unifiedOrderBaseUrl = '\/merchant\/v1\/orders'/) + assert.match(source, /ApiUnifiedOrderMainStatus[\s\S]*?'WAIT_PALLETIZE'/) assert.match(source, /ApiUnifiedOrderMainStatus[\s\S]*?'WAIT_SPLIT'/) + assert.match(source, /fulfillmentType\?: 'STANDARD' \| 'ONE_CLICK_DROPSHIP' \| null/) + assert.match(source, /lxCode\?: string \| null/) assert.match(source, /mainStatus\?: ApiUnifiedOrderMainStatus/) }) diff --git a/src/modules/first-leg/api/unifiedOrders.ts b/src/modules/first-leg/api/unifiedOrders.ts index fad19f2..e32787f 100644 --- a/src/modules/first-leg/api/unifiedOrders.ts +++ b/src/modules/first-leg/api/unifiedOrders.ts @@ -4,6 +4,7 @@ import type { ApiContainerMode, ApiDestinationType, ApiFirstLegStatus, ApiPickup export type ApiUnifiedOrderMainStatus = | 'PENDING_REVIEW' | 'WAIT_MEASURE' + | 'WAIT_PALLETIZE' | 'WAIT_SPLIT' | 'LOADING' | 'LOADED' @@ -54,6 +55,8 @@ export interface MerchantUnifiedOrderRow { orderId: number | string tenantId: number | string orderNo: string | null + fulfillmentType?: 'STANDARD' | 'ONE_CLICK_DROPSHIP' | null + lxCode?: string | null mainStatus: ApiUnifiedOrderMainStatus nextAction: string | null internalOrderStatus: ApiFirstLegStatus | null diff --git a/src/modules/first-leg/components/OrderProgressPanel.vue b/src/modules/first-leg/components/OrderProgressPanel.vue index 32198cc..d56f20c 100644 --- a/src/modules/first-leg/components/OrderProgressPanel.vue +++ b/src/modules/first-leg/components/OrderProgressPanel.vue @@ -23,7 +23,7 @@ function stageState(index: number) {

订单进度

- 国内装柜进度及后续履约同步 + 国内入仓、组托、装柜及后续履约同步
{{ getFirstLegStatusLabel(order.status) }} diff --git a/src/modules/first-leg/store/firstLegOrders.test.mjs b/src/modules/first-leg/store/firstLegOrders.test.mjs index 095cb15..9cdeba9 100644 --- a/src/modules/first-leg/store/firstLegOrders.test.mjs +++ b/src/modules/first-leg/store/firstLegOrders.test.mjs @@ -80,6 +80,8 @@ test('declares the final C02 row and detail contract in the API adapter', () => assert.match(unifiedApiSource, /orderId: number \| string/) assert.match(unifiedApiSource, /mainStatus: ApiUnifiedOrderMainStatus/) assert.match(unifiedApiSource, /nextAction: string \| null/) + assert.match(unifiedApiSource, /fulfillmentType\?: 'STANDARD' \| 'ONE_CLICK_DROPSHIP' \| null/) + assert.match(unifiedApiSource, /lxCode\?: string \| null/) assert.match(unifiedApiSource, /customerRemark: string \| null/) for (const field of ['pickupMethod', 'requestedPickupAt', 'containerMode', 'destinationType', 'destinationCode', 'skuNos', 'totalQuantity', 'totalNetWeightKg', 'chargeableVolumeCbm', 'inboundCompletedAt', 'shipmentId', 'referenceId']) { assert.match(unifiedApiSource, new RegExp(`${field}:`)) @@ -117,6 +119,10 @@ test('maps C02 authoritative status, transport, cargo and pickup snapshots', () assert.match(storeSource, /id: String\(summary\.orderId\)/) assert.match(storeSource, /transportMode: unifiedTransport\(summary\.containerMode\)/) assert.match(storeSource, /status: summary\.mainStatus/) + assert.match(storeSource, /fulfillmentType: summary\.fulfillmentType \?\? 'STANDARD'/) + assert.match(storeSource, /lxCode: summary\.lxCode \?\? ''/) + assert.match(storeSource, /mapped\.fulfillmentType = detail\.summary\.fulfillmentType \?\? mapped\.fulfillmentType/) + assert.match(storeSource, /mapped\.lxCode = detail\.summary\.lxCode \?\? mapped\.lxCode/) assert.match(storeSource, /requestedPickupAt: summary\.requestedPickupAt \?\? ''/) assert.match(storeSource, /chargeableVolumeCbm: summary\.chargeableVolumeCbm \?\? null/) assert.match(storeSource, /inboundedAt: summary\.inboundCompletedAt \?\? ''/) @@ -127,6 +133,7 @@ test('maps C02 authoritative status, transport, cargo and pickup snapshots', () assert.match(storeSource, /summary\?\.loading\?\.pickingVersion/) assert.match(storeSource, /summary\?\.inboundId == null \? '' : String\(summary\.inboundId\)/) assert.match(storeSource, /customerRemark: summary\.customerRemark \?\? ''/) + assert.match(storeSource, /PALLETIZE_DROPSHIP: '一件代发组托'/) }) test('sends a manually entered old-system inbound code with the draft payload', () => { diff --git a/src/modules/first-leg/store/firstLegOrders.ts b/src/modules/first-leg/store/firstLegOrders.ts index 732728f..5fc9e54 100644 --- a/src/modules/first-leg/store/firstLegOrders.ts +++ b/src/modules/first-leg/store/firstLegOrders.ts @@ -231,6 +231,7 @@ const unifiedActionLabels: Readonly> = { SUBMIT_ORDER: '订单已提交', APPROVE_ORDER: '订单审核通过', CONFIRM_MEASUREMENT: '测量数据已确认', + PALLETIZE_DROPSHIP: '一件代发组托', SMART_SPLIT: '分柜方案已更新', CONFIRM_PRELOAD: '预装清单已确认', COMPLETE_LOADING: '装柜已完成', @@ -279,7 +280,7 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar return { id: String(order.id ?? order.orderId ?? ''), orderNo: order.orderNo, referenceNo: order.referenceId ?? '', transportMode: order.transportMode ? clientTransport(order) : null, - businessType: 'EXPORT', fulfillmentType: order.fulfillmentType ?? 'STANDARD', origin: shipper?.city ?? '', destination: order.destinationWarehouseName || order.destinationCountryCode || '', + businessType: 'EXPORT', fulfillmentType: order.fulfillmentType ?? 'STANDARD', lxCode: order.lxCode ?? '', origin: shipper?.city ?? '', destination: order.destinationWarehouseName || order.destinationCountryCode || '', receivingWarehouseId: String(order.receivingWarehouseId), receivingWarehouseName: order.receivingWarehouseName, receivingWarehouseSnapshot: orderWarehouseSnapshot(order, warehouse), expectedDepartureDate: order.expectedShipDate ?? '', etd: '', eta: '', shipperPartyId: shipper?.id ?? '', shipperPartySnapshot: shipper, shipper: shipper?.name ?? '', @@ -328,7 +329,7 @@ function mapUnifiedOrderRow(summary: MerchantUnifiedOrderRow, warehouses: Platfo const warehouse = warehouses.find((item) => item.id === String(summary.receivingWarehouseId)) return { id: String(summary.orderId), orderNo: summary.orderNo, referenceNo: summary.referenceId ?? '', transportMode: unifiedTransport(summary.containerMode), - businessType: 'EXPORT', origin: '', destination: summary.destinationWarehouseName ?? '', + businessType: 'EXPORT', fulfillmentType: summary.fulfillmentType ?? 'STANDARD', lxCode: summary.lxCode ?? '', origin: '', destination: summary.destinationWarehouseName ?? '', receivingWarehouseId: summary.receivingWarehouseId == null ? '' : String(summary.receivingWarehouseId), receivingWarehouseName: summary.receivingWarehouseName ?? '', receivingWarehouseSnapshot: warehouseSnapshot(warehouse), expectedDepartureDate: summary.expectedShipDate ?? '', etd: '', eta: '', shipperPartyId: '', shipperPartySnapshot: null, @@ -381,6 +382,8 @@ function mapUnifiedOrderDetail(detail: MerchantUnifiedOrderDetail, warehouses: P const mapped = mapOrder(detail.order.order, warehouses, detail.order) mapped.id = String(detail.summary.orderId) mapped.status = detail.summary.mainStatus + mapped.fulfillmentType = detail.summary.fulfillmentType ?? mapped.fulfillmentType + mapped.lxCode = detail.summary.lxCode ?? mapped.lxCode mapped.businessSummary = businessSummary(detail.summary) mapped.timeline = unifiedTimeline(detail) const summary = mapUnifiedOrderRow(detail.summary, warehouses) diff --git a/src/modules/first-leg/types/inboundBilling.ts b/src/modules/first-leg/types/inboundBilling.ts index bb34de7..101a2a5 100644 --- a/src/modules/first-leg/types/inboundBilling.ts +++ b/src/modules/first-leg/types/inboundBilling.ts @@ -6,6 +6,7 @@ export type MerchantInboundProcessStatus = | 'WAIT_PRICING' | 'FEE_CONFIRMING' | 'INBOUNDING' + | 'WAIT_PALLETIZE' | 'WAIT_SPLIT' | 'VOIDED' diff --git a/src/modules/first-leg/utils/firstLegOrder.test.mjs b/src/modules/first-leg/utils/firstLegOrder.test.mjs index 6ce01cf..65a4c79 100644 --- a/src/modules/first-leg/utils/firstLegOrder.test.mjs +++ b/src/modules/first-leg/utils/firstLegOrder.test.mjs @@ -5,6 +5,8 @@ import { canClientCancelFirstLegOrder, canClientEditFirstLegOrder, createEmptyCargoItem, + FIRST_LEG_PROGRESS_STAGES, + getFirstLegProgressIndex, getFirstLegStatusLabel, getFirstLegTransportModeLabel, summarizeFirstLegCargo, @@ -58,12 +60,16 @@ function validDraft() { test('uses the confirmed status and transport labels', () => { assert.equal(getFirstLegStatusLabel('WAIT_MEASURE'), '待测量数据') - assert.equal(getFirstLegStatusLabel('WAIT_SPLIT'), '待分柜') + assert.equal(getFirstLegStatusLabel('WAIT_PALLETIZE'), '待组托') assert.equal(getFirstLegStatusLabel('WAIT_SPLIT'), '待分柜') assert.equal(getFirstLegStatusLabel('LOADING'), '装柜中') assert.equal(getFirstLegStatusLabel('LOADED'), '装柜完成') assert.equal(getFirstLegStatusLabel('DELIVERED'), '派送完成') assert.equal(getFirstLegTransportModeLabel('FCL'), '海运整箱 FCL') + assert.deepEqual(FIRST_LEG_PROGRESS_STAGES.map((item) => item.code), [ + 'PENDING_REVIEW', 'WAIT_MEASURE', 'WAIT_PALLETIZE', 'WAIT_SPLIT', 'LOADING', 'LOADED', 'DELIVERED', + ]) + assert.equal(getFirstLegProgressIndex('WAIT_PALLETIZE'), 2) }) test('only allows draft orders to be edited in the confirmed phase-one flow', () => { @@ -72,6 +78,7 @@ test('only allows draft orders to be edited in the confirmed phase-one flow', () assert.equal(canClientCancelFirstLegOrder('DRAFT'), true) assert.equal(canClientCancelFirstLegOrder('PENDING_REVIEW'), true) assert.equal(canClientCancelFirstLegOrder('WAIT_MEASURE'), false) + assert.equal(canClientCancelFirstLegOrder('WAIT_PALLETIZE'), false) assert.equal(canClientCancelFirstLegOrder('WAIT_SPLIT'), false) }) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs index 351c9c9..5653e00 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.test.mjs +++ b/src/modules/first-leg/views/FirstLegOrdersView.test.mjs @@ -199,7 +199,7 @@ test('shows one unified progress line and C02 business summaries without detail assert.match(source, /summary\.expectedPickCount/) assert.match(source, /summary\.deliveryTrackingNo/) assert.match(progressSource, /FIRST_LEG_PROGRESS_STAGES/) - assert.match(progressSource, /国内装柜进度及后续履约同步/) + assert.match(progressSource, /国内入仓、组托、装柜及后续履约同步/) assert.doesNotMatch(progressSource, /进度记录|el-timeline|timeline/) assert.doesNotMatch(source, /

报关回执<\/h3>/) assert.match(source, /船期摘要/) @@ -369,6 +369,12 @@ test('distills order creation into the confirmed prototype sections with server assert.doesNotMatch(source, /v-model="form\.(?:businessType|origin|destination|priority|tradeTerm)"/) }) +test('shows an optional LX inbound order number for one-click dropship orders', () => { + assert.match(source, /v-if="form\.fulfillmentType === 'ONE_CLICK_DROPSHIP'" label="LX入库单号" prop="lxCode"/) + assert.match(source, /v-model="form\.lxCode"[^>]*placeholder="请输入LX入库单号"/) + assert.doesNotMatch(source, /label="LX入库单号"[^>]*required/) +}) + test('loads only active server channels for the selected domestic warehouse', () => { assert.match(source, /getMerchantPricingChannels\(warehouseId\)/) assert.match(source, /const channelOptions = ref\(\[\]\)/) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue index 1b66bbf..f9f67a0 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.vue +++ b/src/modules/first-leg/views/FirstLegOrdersView.vue @@ -1898,7 +1898,7 @@ async function searchOrders(model: QueryModel) {

客户下单

先选择货物如何送到国内收货仓,并填写本次运输安排。

普通头程一件代发测量完成后不办理国内库存入库,将直接推送 PDA 合托。 - 用于绑定领星入库单,提交后可在尾程订单、入库单和出库单中查询。 + 用于绑定领星入库单,提交后可在尾程订单、入库单和出库单中查询。 快递自送我司提货卡车自送 @@ -260,14 +264,18 @@ async function openDetail(value: Record) { + +

货物明细

添加行清空

模板每行代表一箱;上传后直接回显到下方明细。明细目的仓优先,留空时使用顶部统一目的海外仓。

-
商品 SKU *中文品名 *可用箱数{{ props.service === 'labeling' ? 'FBA 货件编号 *' : 'FNSKU / 箱唛' }}换标新 SKU *{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *操作
{{ row.availableQuantity }} 箱
{{ props.service === 'disposal' ? '请先选择入库单,系统将自动带出 SKU 和可用箱数' : '暂无货物明细,请点击“添加行”' }}
+ +
操作商品 SKU *中文品名 *可用箱数{{ props.service === 'labeling' ? 'FBA 货件编号 *' : 'FNSKU / 箱唛' }}换标新 SKU *{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *
{{ row.availableQuantity }} 箱
{{ props.service === 'disposal' ? '请先选择入库单,系统将自动带出 SKU 和可用箱数' : '暂无货物明细,请点击“添加行”' }}
+
重置提交{{ config.title }}