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) {
客户下单
先选择货物如何送到国内收货仓,并填写本次运输安排。
@@ -260,14 +264,18 @@ async function openDetail(value: Record) {
+
+
重置提交{{ config.title }}