This commit is contained in:
2026-08-13 17:12:05 +08:00
parent 5aa060def2
commit fb6465104d
13 changed files with 776 additions and 220 deletions
@@ -6,6 +6,22 @@ const storeSource = readFileSync(new URL('./firstLegOrders.ts', import.meta.url)
const apiSource = readFileSync(new URL('../api/firstLeg.ts', import.meta.url), 'utf8')
const unifiedApiSource = readFileSync(new URL('../api/unifiedOrders.ts', import.meta.url), 'utf8')
test('maps the first-leg aggregate list fields without loading detail rows', () => {
assert.match(apiSource, /interface MerchantFirstLegOrderRow/)
assert.match(apiSource, /skuNos: string \| string\[\] \| null/)
assert.match(apiSource, /cargoes\?: Array<\{ skuNo\?: string \| null \}>/)
assert.match(apiSource, /apiRequest<PageResult<MerchantFirstLegOrderRow>>/)
assert.match(storeSource, /function mapFirstLegOrderRow/)
for (const field of ['inboundId', 'inboundCode', 'channelName', 'destinationWarehouseName', 'packageCount', 'expressCompany', 'trackingNo', 'totalGrossWeightKg', 'totalNetWeightKg', 'totalVolumeCbm', 'totalQuantity', 'remark', 'shipmentId', 'referenceId', 'createdAt']) {
assert.match(storeSource, new RegExp(`row\\.${field}`))
}
assert.match(storeSource, /row\.skuNos/)
assert.match(storeSource, /row\.cargoes/)
assert.match(storeSource, /chargeableWeightKg: null/)
assert.match(storeSource, /chargeableVolumeCbm: null/)
assert.match(storeSource, /orders\.value = orderResult\.value\.records\.map\(mapFirstLegOrderRow\)/)
})
test('declares the documented merchant warehouse and first-leg endpoints', () => {
assert.match(apiSource, /url: '\/merchant\/v1\/warehouses'/)
assert.match(apiSource, /url: '\/merchant\/v1\/orders\/first-leg'/)
@@ -16,6 +32,13 @@ test('declares the documented merchant warehouse and first-leg endpoints', () =>
assert.match(apiSource, /url: '\/merchant\/v1\/first-leg\/pricing\/channels'/)
assert.match(apiSource, /params: \{ originWarehouseId \}/)
assert.match(apiSource, /url: '\/merchant\/v1\/first-leg\/pricing\/estimate'/)
assert.match(apiSource, /`\/merchant\/v1\/first-leg\/inbounds\/\$\{encodeURIComponent\(String\(inboundId\)\)\}\/carton-label-pdf`/)
assert.match(apiSource, /`\/merchant\/v1\/first-leg\/orders\/\$\{encodeURIComponent\(String\(orderId\)\)\}\/carton-label-pdf`/)
assert.match(apiSource, /url: '\/merchant\/v1\/first-leg\/orders\/carton-label-pdf'/)
assert.match(apiSource, /data: orderIds\.map\(String\)/)
assert.doesNotMatch(apiSource, /`\/merchant\/v1\/inbounds\/\$\{encodeURIComponent\(String\(inboundId\)\)\}\/carton-label-pdf`/)
assert.match(apiSource, /signature === '%PDF-'/)
assert.match(apiSource, /throw new ApiError\('箱唛服务未返回有效的 PDF 文件', 'INVALID_PDF'\)/)
})
test('maps transport and status fields instead of treating container mode as transport mode', () => {
+54 -52
View File
@@ -13,6 +13,7 @@ import {
type FirstLegOrderPartyPayload,
type FirstLegOrderSaveRequest,
type MerchantFirstLegOrder,
type MerchantFirstLegOrderRow,
type MerchantFirstLegOrderDetail,
type MerchantBatchDraftResponse,
type MerchantWarehouse,
@@ -305,7 +306,10 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar
receivingWarehouseSnapshot: orderWarehouseSnapshot(order, warehouse), expectedDepartureDate: order.expectedShipDate ?? '', etd: '', eta: '',
shipperPartyId: shipper?.id ?? '', shipperPartySnapshot: shipper, shipper: shipper?.name ?? '',
consigneePartyId: consignee?.id ?? '', consigneePartySnapshot: consignee, consignee: consignee?.name ?? '',
expressCompanyName: order.expressCompany ?? '', trackingNumber: order.expressTrackingNo ?? '', priority: 'NORMAL', customerRemark: order.customerRemark ?? '', operatorName: '',
expressCompanyName: order.expressCompany ?? '', trackingNumber: order.expressTrackingNo ?? '', skuNos: order.skuNos ?? [],
totalNetWeightKg: order.totalNetWeightKg ?? null, totalQuantity: order.totalQuantity ?? null,
chargeableVolumeCbm: null, inboundedAt: order.inboundedAt ?? '', businessDate: '',
priority: 'NORMAL', customerRemark: order.customerRemark ?? '', operatorName: '',
status: clientStatus(order.status), version: order.version ?? 0,
items: (detail?.cargoes ?? []).map((item) => ({
id: String(item.id), sku: item.skuNo ?? '', upc: item.upcNo ?? '', cargoNameCn: item.cargoName, cargoNameEn: item.cargoNameEn ?? '',
@@ -323,7 +327,14 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar
productHeightCm: item.productHeightCm ?? 0, shipmentId: item.shipmentId ?? order.shipmentId ?? '',
referenceId: item.referenceId ?? order.referenceId ?? '',
})),
timeline: detail ? timeline(detail) : [], businessSummary: businessSummary(), createdAt: order.createdAt, updatedAt: order.updatedAt,
timeline: detail ? timeline(detail) : [], businessSummary: {
...businessSummary(),
totalPackageCount: order.totalPackageCount ?? null,
actualGrossWeightKg: order.totalGrossWeightKg ?? null,
actualVolumeCbm: order.totalVolumeCbm ?? null,
chargeableWeightKg: order.chargeableWeightKg ?? null,
inboundCode: order.inboundCode ?? '',
}, createdAt: order.createdAt, updatedAt: order.updatedAt,
tradeTerm: order.tradeTerm ?? '', destinationCountryCode: order.destinationCountryCode,
destinationWarehouseId: order.destinationWarehouseId ? String(order.destinationWarehouseId) : '',
destinationWarehouseName: order.destinationWarehouseName, pickupMethod: order.pickupMethod, channelCode: order.channelCode,
@@ -337,23 +348,55 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar
function mapUnifiedOrderRow(summary: MerchantUnifiedOrderRow, warehouses: PlatformReceivingWarehouse[]): FirstLegOrder {
const warehouse = warehouses.find((item) => item.id === String(summary.receivingWarehouseId))
return {
id: String(summary.orderId), orderNo: summary.orderNo, referenceNo: '', transportMode: null,
id: String(summary.orderId), orderNo: summary.orderNo, referenceNo: summary.referenceId ?? '', transportMode: null,
businessType: 'EXPORT', 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,
shipper: '', consigneePartyId: '', consigneePartySnapshot: null, consignee: '', expressCompanyName: '',
trackingNumber: summary.delivery?.trackingNo ?? '', priority: 'NORMAL', customerRemark: '', operatorName: '',
shipper: '', consigneePartyId: '', consigneePartySnapshot: null, consignee: '', expressCompanyName: summary.expressCompany ?? '',
trackingNumber: summary.delivery?.trackingNo ?? '', skuNos: summary.skuNos ?? [], totalNetWeightKg: summary.totalNetWeightKg ?? null,
totalQuantity: summary.totalQuantity ?? null, chargeableVolumeCbm: null,
inboundedAt: summary.inboundedAt ?? '', businessDate: '', priority: 'NORMAL', customerRemark: '', operatorName: '',
status: summary.mainStatus, version: 0, items: [], timeline: [], businessSummary: businessSummary(summary),
createdAt: summary.createdAt, updatedAt: summary.updatedAt, tradeTerm: '', destinationCountryCode: '',
destinationWarehouseId: summary.destinationWarehouseId == null ? '' : String(summary.destinationWarehouseId),
destinationWarehouseName: summary.destinationWarehouseName ?? '', pickupMethod: 'SELF_EXPRESS', channelCode: '',
channelName: summary.channelName ?? '', pricingTaxMode: 'TAX_INCLUDED', carrierId: '', carrierName: '', taxMode: 'BUY_DECLARATION', customsInspection: false,
destinationType: 'PRIVATE', amazonWarehouseCode: '', shipmentId: '',
destinationType: 'PRIVATE', amazonWarehouseCode: '', shipmentId: summary.shipmentId ?? '',
receivingTime: '', submittedAt: '', completedAt: '',
}
}
function mapFirstLegOrderRow(row: MerchantFirstLegOrderRow): FirstLegOrder {
const skuNos = Array.isArray(row.skuNos)
? row.skuNos
: (row.skuNos ?? '').split('、').map((value) => value.trim()).filter(Boolean)
const resolvedSkuNos = skuNos.length ? skuNos : (row.cargoes ?? [])
.map((cargo) => cargo.skuNo?.trim())
.filter((value): value is string => Boolean(value))
const uniqueSkuNos = [...new Set(resolvedSkuNos)]
return {
id: String(row.orderId), orderNo: row.orderNo, referenceNo: row.referenceId ?? '', transportMode: null,
businessType: 'EXPORT', origin: '', destination: row.destinationWarehouseName ?? '', receivingWarehouseId: '',
receivingWarehouseName: '', receivingWarehouseSnapshot: null, expectedDepartureDate: '', etd: '', eta: '',
shipperPartyId: '', shipperPartySnapshot: null, shipper: '', consigneePartyId: '', consigneePartySnapshot: null,
consignee: row.receivingCompanyName ?? '', expressCompanyName: row.expressCompany ?? '', trackingNumber: row.trackingNo ?? '',
skuNos: uniqueSkuNos, totalNetWeightKg: row.totalNetWeightKg, totalQuantity: row.totalQuantity,
chargeableVolumeCbm: null, inboundedAt: row.inboundedAt ?? '', businessDate: '', priority: 'NORMAL', customerRemark: row.remark ?? '',
operatorName: '', status: clientStatus(row.status), version: 0, items: [], timeline: [],
businessSummary: {
...businessSummary(), totalPackageCount: row.packageCount, actualGrossWeightKg: row.totalGrossWeightKg ?? row.weightKg,
actualVolumeCbm: row.totalVolumeCbm, chargeableWeightKg: null,
inboundId: row.inboundId == null ? '' : String(row.inboundId), inboundCode: row.inboundCode ?? '',
},
createdAt: row.createdAt, updatedAt: row.createdAt, tradeTerm: '', destinationCountryCode: '', destinationWarehouseId: '',
destinationWarehouseName: row.destinationWarehouseName ?? '', pickupMethod: 'SELF_EXPRESS', channelCode: '',
channelName: row.channelName ?? '', pricingTaxMode: 'TAX_INCLUDED', carrierId: '', carrierName: '', taxMode: 'BUY_DECLARATION',
customsInspection: false, destinationType: row.destinationType ?? 'PRIVATE', amazonWarehouseCode: '',
shipmentId: row.shipmentId ?? row.internationalOrderNo ?? '', receivingTime: '', submittedAt: '', completedAt: '',
}
}
function mapUnifiedOrderDetail(detail: MerchantUnifiedOrderDetail, warehouses: PlatformReceivingWarehouse[]): FirstLegOrder {
if (!detail?.summary || !detail.order?.order) throw new Error('订单详情数据不完整,请刷新列表后重试')
const mapped = mapOrder(detail.order.order, warehouses, detail.order)
@@ -366,8 +409,7 @@ function mapUnifiedOrderDetail(detail: MerchantUnifiedOrderDetail, warehouses: P
}
type OrderListReadResult =
| { source: 'unified'; records: MerchantUnifiedOrderRow[]; drafts: MerchantFirstLegOrder[]; draftError: string }
| { source: 'legacy'; records: MerchantFirstLegOrder[] }
| { source: 'first-leg'; records: MerchantFirstLegOrderRow[] }
interface ClientOrderReadQuery extends MerchantUnifiedOrderQuery {
draftOnly?: boolean
@@ -384,41 +426,9 @@ function legacyQuery(query: ClientOrderReadQuery, status?: ApiFirstLegStatus) {
}
async function getOrdersForClient(query: ClientOrderReadQuery = {}): Promise<OrderListReadResult> {
if (query.draftOnly) {
const drafts = await getMerchantFirstLegOrders(legacyQuery(query, 'DRAFT'))
return { source: 'legacy', records: drafts.records }
}
const includeDrafts = query.includeDrafts ?? true
const unifiedQuery: MerchantUnifiedOrderQuery = {
keyword: query.keyword,
orderNo: query.orderNo,
mainStatus: query.mainStatus,
receivingWarehouseId: query.receivingWarehouseId,
destinationWarehouseId: query.destinationWarehouseId,
createdFrom: query.createdFrom,
createdTo: query.createdTo,
page: query.page,
size: query.size,
}
try {
const response = await getMerchantUnifiedOrders(unifiedQuery)
if (!includeDrafts) return { source: 'unified', records: response.records, drafts: [], draftError: '' }
try {
const drafts = await getMerchantFirstLegOrders(legacyQuery(query, 'DRAFT'))
return { source: 'unified', records: response.records, drafts: drafts.records, draftError: '' }
} catch (error) {
return {
source: 'unified',
records: response.records,
drafts: [],
draftError: error instanceof Error ? error.message : '订单草稿加载失败',
}
}
} catch (error) {
if (!isUnifiedOrderQueryUnavailable(error)) throw error
const response = await getMerchantFirstLegOrders(legacyQuery(query))
return { source: 'legacy', records: response.records }
}
const status = query.draftOnly ? 'DRAFT' : undefined
const response = await getMerchantFirstLegOrders(legacyQuery(query, status))
return { source: 'first-leg', records: response.records }
}
function mapWarehouse(item: MerchantWarehouse): ClientWarehouse {
@@ -483,15 +493,7 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
const [orderResult, warehouseResult] = await Promise.allSettled([getOrdersForClient(query), loadWarehouseType('DOMESTIC', true)])
if (warehouseResult.status === 'rejected' && !warehouseLoadError.value) warehouseLoadError.value = warehouseResult.reason instanceof Error ? warehouseResult.reason.message : '公共仓库数据加载失败'
if (orderResult.status === 'fulfilled') {
if (orderResult.value.source === 'unified') {
draftLoadError.value = orderResult.value.draftError
orders.value = [
...orderResult.value.drafts.map((item) => mapOrder(item, warehouses.value)),
...orderResult.value.records.map((item) => mapUnifiedOrderRow(item, warehouses.value)),
].sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))
} else {
orders.value = orderResult.value.records.map((item) => mapOrder(item, warehouses.value))
}
orders.value = orderResult.value.records.map(mapFirstLegOrderRow)
} else {
loadError.value = orderResult.reason instanceof Error ? orderResult.reason.message : '订单数据加载失败'
}