import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { createMerchantFirstLegOrderBatch, createMerchantFirstLegOrder, createIdempotencyKey, getMerchantFirstLegOrder, getMerchantFirstLegOrders, getMerchantWarehouseDetail, getMerchantWarehouses, submitMerchantFirstLegOrder, updateMerchantFirstLegOrder, type ApiFirstLegStatus, type FirstLegOrderPartyPayload, type FirstLegOrderSaveRequest, type MerchantFirstLegOrder, type MerchantFirstLegOrderRow, type MerchantFirstLegOrderDetail, type MerchantBatchDraftResponse, type MerchantWarehouse, } from '@/modules/first-leg/api/firstLeg' import { getMerchantUnifiedOrder, getMerchantUnifiedOrders, isUnifiedOrderQueryUnavailable, type MerchantUnifiedOrderDetail, type MerchantUnifiedOrderQuery, type MerchantUnifiedOrderRow, } from '@/modules/first-leg/api/unifiedOrders' import type { MerchantTenantStatus } from '@/modules/tenant-management/public' import type { PlatformReceivingWarehouse, PlatformReceivingWarehouseSnapshot } from '@chongchong/shared' import type { FirstLegPartySnapshot } from '@/modules/first-leg/data/firstLegParties' import type { FirstLegOrder, FirstLegOrderDraft, FirstLegOrderTimelineItem } from '@/modules/first-leg/types/firstLegOrder' import { canClientEditFirstLegOrder, getFirstLegStatusLabel } from '@/modules/first-leg/utils/firstLegOrder' import { loadAllWarehouseRecords } from '@/modules/first-leg/utils/warehousePagination' export type ClientWarehouseType = 'DOMESTIC' | 'FBA' | 'OVERSEAS' export type ClientWarehouse = PlatformReceivingWarehouse & { warehouseType: ClientWarehouseType countryCode: string } const countryCodes: Readonly> = { 中国: 'CN', 美国: 'US', 德国: 'DE', 英国: 'GB', 加拿大: 'CA', 日本: 'JP', 澳大利亚: 'AU', } function clientStatus(status: ApiFirstLegStatus): FirstLegOrder['status'] { if (status === 'DRAFT') return 'DRAFT' if (status === 'SUBMITTED') return 'PENDING_REVIEW' // 后端联调任务曾出现 REJECTED,但一期已确认订单不设驳回状态;兼容旧数据时仍按待审核展示。 if (status === 'REJECTED') return 'PENDING_REVIEW' if (status === 'APPROVED' || status === 'ACCEPTED' || status === 'PROCESSING') return 'WAIT_MEASURE' if (status === 'COMPLETED') return 'LOADED' if (status === 'CANCELLED') return 'PENDING_REVIEW' return 'PENDING_REVIEW' } function clientTransport(order: MerchantFirstLegOrder): FirstLegOrder['transportMode'] { if (order.transportMode === 'SEA') return order.containerMode === 'FULL_CONTAINER' ? 'FCL' : 'LCL' if (order.transportMode === 'AIR') return 'AIR' return 'ROAD' } function apiTransport(draft: FirstLegOrderDraft): FirstLegOrderSaveRequest['transportMode'] { if (draft.transportMode === 'AIR') return 'AIR' if (draft.transportMode === 'ROAD') return 'TRUCK' return 'SEA' } function partyPayload(snapshot: FirstLegPartySnapshot): FirstLegOrderPartyPayload { return { companyName: snapshot.name, contactName: snapshot.contactName, phone: snapshot.phone, email: snapshot.email || undefined, countryCode: countryCodes[snapshot.countryRegion] ?? snapshot.countryRegion.toUpperCase().slice(0, 2), province: snapshot.province || undefined, city: snapshot.city, addressLine: snapshot.address, postalCode: snapshot.postalCode || undefined, } } function savePayload(draft: FirstLegOrderDraft, warehouse: ClientWarehouse, customerName: string): FirstLegOrderSaveRequest { const sender = draft.pickupMethod === 'PICKUP' && draft.shipperPartySnapshot ? partyPayload({ ...draft.shipperPartySnapshot, name: draft.shipperPartySnapshot.name || customerName.trim() || '客户', countryRegion: draft.shipperPartySnapshot.countryRegion || 'CN', }) : undefined const receiver = draft.destinationType === 'PRIVATE' && draft.consigneePartySnapshot ? partyPayload({ ...draft.consigneePartySnapshot, name: draft.destinationWarehouseName, countryRegion: draft.destinationCountryCode.trim().toUpperCase(), }) : undefined return { customerName: customerName.trim(), transportMode: apiTransport(draft), tradeTerm: draft.tradeTerm || undefined, destinationCountryCode: draft.destinationCountryCode.trim().toUpperCase(), destinationWarehouseId: draft.destinationType === 'PRIVATE' ? undefined : draft.destinationWarehouseId, destinationWarehouseName: draft.destinationWarehouseName, expectedShipDate: draft.expectedDepartureDate || undefined, customerRemark: draft.customerRemark || undefined, sender, receiver, cargoes: draft.items.map((item) => ({ cargoName: item.cargoNameCn, cargoNameEn: item.cargoNameEn || undefined, hsCode: item.hsCode || undefined, customsHsCode: item.customsHsCode || undefined, packageType: item.packageType, packageCount: item.packageCount, grossWeightKg: item.grossWeightKg, volumeCbm: item.volumeCbm, lengthCm: item.lengthCm || undefined, widthCm: item.widthCm || undefined, heightCm: item.heightCm || undefined, declaredValue: item.declaredValue || undefined, currency: item.currency || undefined, dangerousGoods: item.dangerousGoods, cargoRemark: item.remark || undefined, skuNo: item.sku || undefined, upcNo: item.upc || undefined, totalQuantity: item.pieces, unitsPerPackage: item.unitsPerPackage || undefined, netWeightKg: item.netWeightKg || undefined, unitDeclaredValue: item.unitDeclaredValue || undefined, material: item.material || undefined, usage: item.usage || undefined, model: item.model || undefined, brand: item.brand || undefined, productLengthCm: item.productLengthCm || undefined, productWidthCm: item.productWidthCm || undefined, productHeightCm: item.productHeightCm || undefined, shipmentId: item.shipmentId || undefined, referenceId: item.referenceId || undefined, productImageFileId: item.productImageFileId || undefined, })), pickupMethod: draft.pickupMethod, receivingWarehouseId: warehouse.id, receivingWarehouseName: warehouse.name, channelCode: draft.channelCode, channelName: draft.channelName, pricingTaxMode: draft.pricingTaxMode, taxMode: draft.taxMode, containerMode: draft.transportMode === 'FCL' ? 'FULL_CONTAINER' : draft.transportMode === 'LCL' ? 'LCL' : undefined, customsInspection: draft.customsInspection, destinationType: draft.destinationType, amazonWarehouseCode: draft.destinationType === 'FBA' ? draft.amazonWarehouseCode || undefined : undefined, shipmentId: draft.destinationType === 'FBA' ? draft.items.find((item) => item.shipmentId)?.shipmentId || undefined : undefined, referenceId: draft.items.find((item) => item.referenceId)?.referenceId || draft.referenceNo || undefined, } } function prepareDraftForSave(draft: FirstLegOrderDraft, warehouses: ClientWarehouse[]) { const warehouse = warehouses.find((item) => item.id === draft.receivingWarehouseId) if (!warehouse || warehouse.warehouseType !== 'DOMESTIC') throw new Error('所选国内收货仓当前不可用') if (draft.destinationType !== 'PRIVATE') { const destinationWarehouse = warehouses.find((item) => item.id === draft.destinationWarehouseId && item.warehouseType === draft.destinationType) if (!destinationWarehouse) throw new Error('所选目的仓当前不可用,请重新选择') draft.destinationWarehouseName = destinationWarehouse.name draft.destinationCountryCode = destinationWarehouse.countryCode draft.amazonWarehouseCode = draft.destinationType === 'FBA' ? destinationWarehouse.code : '' } else { draft.destinationWarehouseId = '' } return warehouse } function warehouseSnapshot(warehouse?: PlatformReceivingWarehouse): PlatformReceivingWarehouseSnapshot | null { return warehouse ? { id: warehouse.id, code: warehouse.code, name: warehouse.name, province: warehouse.province, city: warehouse.city, address: warehouse.address } : null } function orderWarehouseSnapshot(order: MerchantFirstLegOrder, warehouse?: PlatformReceivingWarehouse): PlatformReceivingWarehouseSnapshot | null { if (order.receivingWarehouseAddress) { return { id: String(order.receivingWarehouseId), code: warehouse?.code ?? '', name: order.receivingWarehouseName, province: warehouse?.province ?? '', city: warehouse?.city ?? '', address: order.receivingWarehouseAddress, } } return warehouseSnapshot(warehouse) } function partySnapshot(detail: MerchantFirstLegOrderDetail, type: 'SENDER' | 'RECEIVER'): FirstLegPartySnapshot | null { const party = (detail.parties ?? []).find((item) => item.partyType === type) if (!party) return null return { id: String(party.id), code: type, name: party.companyName, role: type === 'SENDER' ? 'SHIPPER' : 'CONSIGNEE', contactName: party.contactName, phone: party.phone, email: party.email ?? '', countryRegion: party.countryCode, province: party.province ?? '', city: party.city, postalCode: party.postalCode ?? '', address: party.addressLine, } } function timeline(detail: MerchantFirstLegOrderDetail): FirstLegOrderTimelineItem[] { return (detail.statusHistory ?? []).map((item) => { const status = clientStatus(item.toStatus) return { id: String(item.id), kind: 'MAIN_STATUS', status: status === 'DRAFT' ? null : status, nodeType: '', title: item.toStatus === 'REJECTED' ? '订单信息已补充' : item.action || '订单进度已更新', description: item.reason ?? '', operatorName: item.operatorName ?? '', occurredAt: item.occurredAt, plannedAt: '', completed: true, attachmentCount: 0, } }) } function businessSummary(summary?: MerchantUnifiedOrderRow): FirstLegOrder['businessSummary'] { return { totalPackageCount: summary?.totalPackageCount ?? null, actualGrossWeightKg: summary?.actualGrossWeightKg ?? null, actualVolumeCbm: summary?.actualVolumeCbm ?? null, chargeableWeightKg: summary?.measurement?.chargeableWeightKg ?? null, measurementCompletedAt: summary?.measurement?.completedAt ?? '', preloadStatus: summary?.preload?.status ?? '', preloadItemCount: summary?.preload?.itemCount ?? null, loadingStatus: summary?.loading?.status ?? '', loadingVersion: summary?.loading?.loadingVersion ?? null, pickingVersion: summary?.loading?.pickingVersion ?? null, expectedPickCount: summary?.loading?.expectedCount ?? null, pickedCount: summary?.loading?.pickedCount ?? null, evidenceCount: summary?.loading?.evidenceCount ?? null, sealNo: summary?.loading?.sealNo ?? '', deliveryStatus: summary?.delivery?.status ?? '', deliveryCompletedAt: summary?.delivery?.completedAt ?? '', deliveryTrackingNo: summary?.delivery?.trackingNo ?? '', containerNo: summary?.transport?.containerNo ?? '', cabinetNo: summary?.transport?.cabinetNo ?? '', vesselName: summary?.transport?.vesselName ?? '', voyageNo: summary?.transport?.voyageNo ?? '', sailingAt: summary?.transport?.sailingAt ?? '', portArrivalAt: summary?.transport?.portArrivalAt ?? '', inboundId: summary?.inboundId == null ? '' : String(summary.inboundId), inboundVersion: summary?.inboundVersion ?? null, inboundCode: summary?.inboundCode ?? '', nextAction: summary?.nextAction ?? '', } } const unifiedActionLabels: Readonly> = { SUBMIT_ORDER: '订单已提交', APPROVE_ORDER: '订单审核通过', CONFIRM_MEASUREMENT: '测量数据已确认', SMART_SPLIT: '分柜方案已更新', CONFIRM_PRELOAD: '预装清单已确认', COMPLETE_LOADING: '装柜已完成', COMPLETE_DELIVERY: '派送已完成', } function unifiedTimeline(detail: MerchantUnifiedOrderDetail): FirstLegOrderTimelineItem[] { const statusItems: FirstLegOrderTimelineItem[] = (detail.timeline ?? []).flatMap((item, index) => item.mainStatus ? [{ id: `status-${item.source}-${item.eventType}-${index}`, kind: 'MAIN_STATUS' as const, status: item.mainStatus, nodeType: '', title: (item.action && unifiedActionLabels[item.action]) || (item.mainStatus ? `进入${getFirstLegStatusLabel(item.mainStatus)}` : '订单进度已更新'), description: item.remark ?? '', operatorName: item.operatorName ?? '', occurredAt: item.occurredAt ?? '', plannedAt: '', completed: true, attachmentCount: 0, }] : []) const customItems: FirstLegOrderTimelineItem[] = (detail.customNodes ?? []) .filter((item) => item.customerVisible) .map((item) => ({ id: `node-${item.id}`, kind: 'CUSTOM_NODE', status: null, nodeType: item.nodeType, title: item.name, description: item.remark ?? '', operatorName: item.operatorName ?? '', occurredAt: item.completedAt ?? item.plannedAt ?? '', plannedAt: item.plannedAt ?? '', completed: Boolean(item.completedAt), attachmentCount: item.attachmentIds?.length ?? 0, })) return [...statusItems, ...customItems].sort((left, right) => { const timeDifference = Date.parse(left.occurredAt) - Date.parse(right.occurredAt) return Number.isNaN(timeDifference) ? 0 : timeDifference }) } function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWarehouse[], detail?: MerchantFirstLegOrderDetail): FirstLegOrder { const warehouse = warehouses.find((item) => item.id === String(order.receivingWarehouseId)) const shipper = detail ? partySnapshot(detail, 'SENDER') : null const consignee = detail ? partySnapshot(detail, 'RECEIVER') : null return { id: String(order.id ?? order.orderId ?? ''), orderNo: order.orderNo, referenceNo: order.referenceId ?? '', transportMode: order.transportMode ? clientTransport(order) : null, businessType: 'EXPORT', 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 ?? '', consigneePartyId: consignee?.id ?? '', consigneePartySnapshot: consignee, consignee: consignee?.name ?? '', 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 ?? '', productImageFileId: item.productImageFileId == null ? '' : String(item.productImageFileId), productImageName: '', productImagePreviewUrl: '', productImageUploading: false, productImageError: '', customsHsCode: item.customsHsCode ?? '', lengthCm: item.lengthCm ?? 0, widthCm: item.widthCm ?? 0, heightCm: item.heightCm ?? 0, unitsPerPackage: item.unitsPerPackage ?? 1, pieces: item.totalQuantity ?? item.packageCount, packageCount: item.packageCount, grossWeightKg: item.grossWeightKg, volumeCbm: item.volumeCbm, remark: item.cargoRemark ?? '', packageType: item.packageType, hsCode: item.hsCode ?? '', declaredValue: item.declaredValue ?? 0, currency: item.currency ?? 'USD', dangerousGoods: item.dangerousGoods ?? false, netWeightKg: item.netWeightKg ?? 0, unitDeclaredValue: item.unitDeclaredValue ?? 0, material: item.material ?? '', usage: item.usage ?? item.productUsage ?? '', model: item.model ?? '', brand: item.brand ?? '', productLengthCm: item.productLengthCm ?? 0, productWidthCm: item.productWidthCm ?? 0, productHeightCm: item.productHeightCm ?? 0, shipmentId: item.shipmentId ?? order.shipmentId ?? '', referenceId: item.referenceId ?? order.referenceId ?? '', })), 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, channelName: order.channelName, carrierId: order.carrierId == null ? '' : String(order.carrierId), carrierName: order.carrierName ?? '', pricingTaxMode: order.pricingTaxMode ?? 'TAX_INCLUDED', taxMode: order.taxMode, customsInspection: order.customsInspection, destinationType: order.destinationType, amazonWarehouseCode: order.amazonWarehouseCode ?? '', shipmentId: order.shipmentId ?? '', receivingTime: order.receivingTime ?? '', submittedAt: order.submittedAt ?? '', completedAt: order.completedAt ?? '', } } 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: 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: summary.expressCompany ?? '', trackingNumber: summary.delivery?.trackingNo ?? '', skuNos: summary.skuNos ?? [], totalNetWeightKg: summary.totalNetWeightKg ?? null, totalQuantity: summary.totalQuantity ?? null, chargeableVolumeCbm: null, inboundedAt: summary.inboundedAt ?? '', businessDate: summary.businessDate ?? '', priority: 'NORMAL', customerRemark: summary.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: 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) mapped.id = String(detail.summary.orderId) mapped.status = detail.summary.mainStatus mapped.businessSummary = businessSummary(detail.summary) mapped.timeline = unifiedTimeline(detail) mapped.trackingNumber = detail.summary.delivery?.trackingNo ?? mapped.trackingNumber return mapped } type OrderListReadResult = | { source: 'first-leg'; records: MerchantFirstLegOrderRow[] } | { source: 'unified'; records: MerchantUnifiedOrderRow[]; drafts: MerchantFirstLegOrderRow[] } interface ClientOrderReadQuery extends MerchantUnifiedOrderQuery { draftOnly?: boolean includeDrafts?: boolean } function legacyQuery(query: ClientOrderReadQuery, status?: ApiFirstLegStatus) { return { keyword: query.keyword, status, createdFrom: query.createdFrom, createdTo: query.createdTo, } } async function getOrdersForClient(query: ClientOrderReadQuery = {}): Promise { if (query.draftOnly) { const response = await getMerchantFirstLegOrders(legacyQuery(query, 'DRAFT')) return { source: 'first-leg', records: response.records } } 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, draftResponse] = await Promise.all([ getMerchantUnifiedOrders(unifiedQuery), query.includeDrafts ? getMerchantFirstLegOrders(legacyQuery(query, 'DRAFT')) : Promise.resolve({ records: [] as MerchantFirstLegOrderRow[] }), ]) return { source: 'unified', records: response.records, drafts: draftResponse.records } } catch (error) { if (!isUnifiedOrderQueryUnavailable(error)) throw error const response = await getMerchantFirstLegOrders(legacyQuery(query)) return { source: 'first-leg', records: response.records } } } function mapWarehouse(item: MerchantWarehouse): ClientWarehouse { return { id: String(item.id), code: item.warehouseCode, name: item.warehouseName, province: '', city: '', address: '', contactName: item.contactName ?? '', contactPhone: item.contactPhone ?? '', businessHours: '', status: item.status, remark: item.remark ?? '', updatedAt: item.updatedAt ?? '', warehouseType: item.warehouseType as ClientWarehouseType, countryCode: item.countryCode, } } export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () => { const orders = ref([]) const pendingSubmitKeys = new Map() const warehouses = ref([]) const loading = ref(false) const loadedFromApi = ref(false) const loadError = ref('') const warehouseLoadError = ref('') const draftLoadError = ref('') const loadedWarehouseTypes = ref([]) const warehouseTypeRequests = new Map>() const activeCount = computed(() => orders.value.filter((item) => item.status !== 'DRAFT' && item.status !== 'DELIVERED').length) const pendingCount = computed(() => orders.value.filter((item) => item.status === 'PENDING_REVIEW').length) function getById(id: string) { return orders.value.find((item) => item.id === id) } async function loadWarehouseType(warehouseType: ClientWarehouseType, force = false) { if (warehouseType === 'DOMESTIC' && force) warehouseLoadError.value = '' if (!force && loadedWarehouseTypes.value.includes(warehouseType)) return const pending = warehouseTypeRequests.get(warehouseType) if (pending) return pending const request = (async () => { const records = await loadAllWarehouseRecords(warehouseType, getMerchantWarehouses) return records .filter((item) => item.status === 'ACTIVE' && item.warehouseType === warehouseType) .map(mapWarehouse) })() warehouseTypeRequests.set(warehouseType, request) try { const nextWarehouses = await request warehouses.value = [...warehouses.value.filter((item) => item.warehouseType !== warehouseType), ...nextWarehouses] loadedWarehouseTypes.value = [...loadedWarehouseTypes.value.filter((item) => item !== warehouseType), warehouseType] } catch (error) { if (warehouseType === 'DOMESTIC') warehouseLoadError.value = error instanceof Error ? error.message : '公共仓库数据加载失败' throw error } finally { if (warehouseTypeRequests.get(warehouseType) === request) warehouseTypeRequests.delete(warehouseType) } } async function load(query: ClientOrderReadQuery = {}) { loading.value = true loadedFromApi.value = true loadError.value = '' warehouseLoadError.value = '' draftLoadError.value = '' loadedWarehouseTypes.value = [] warehouseTypeRequests.clear() orders.value = [] warehouses.value = [] 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') { orders.value = orderResult.value.source === 'unified' ? [ ...orderResult.value.drafts.map(mapFirstLegOrderRow), ...orderResult.value.records.map((item) => mapUnifiedOrderRow(item, warehouses.value)), ] : orderResult.value.records.map(mapFirstLegOrderRow) } else { loadError.value = orderResult.reason instanceof Error ? orderResult.reason.message : '订单数据加载失败' } loading.value = false } function upsertOrder(mapped: FirstLegOrder) { const index = orders.value.findIndex((item) => item.id === mapped.id) if (index >= 0) orders.value[index] = mapped else orders.value.unshift(mapped) return mapped } async function loadLegacyDetail(id: string) { const detail = await getMerchantFirstLegOrder(id) return upsertOrder(mapOrder(detail.order, warehouses.value, detail)) } async function loadUnifiedDetail(id: string) { try { const detail = await getMerchantUnifiedOrder(id) return upsertOrder(mapUnifiedOrderDetail(detail, warehouses.value)) } catch (error) { if (!isUnifiedOrderQueryUnavailable(error)) throw error return loadLegacyDetail(id) } } async function loadDetail(id: string) { return getById(id)?.status === 'DRAFT' ? loadLegacyDetail(id) : loadUnifiedDetail(id) } async function loadWarehouseDetail(id: string) { const detail = await getMerchantWarehouseDetail(id) const mapped = mapWarehouse(detail.warehouse) const address = detail.addresses[0] const receivingCapability = detail.capabilities.find((item) => item.enabled && item.cutoffTime) if (address) { mapped.province = address.stateProvince ?? '' mapped.city = address.city ?? '' mapped.address = [address.district, address.addressLine1, address.addressLine2].filter(Boolean).join(' ') } if (receivingCapability?.cutoffTime) mapped.businessHours = `每日 ${receivingCapability.cutoffTime.slice(0, 5)} 前` const index = warehouses.value.findIndex((item) => item.id === id) if (index >= 0) warehouses.value[index] = mapped return mapped } async function saveDraft(draft: FirstLegOrderDraft, customerName: string, id?: string) { const warehouse = prepareDraftForSave(draft, warehouses.value) if (id) { const order = getById(id) if (!order || !canClientEditFirstLegOrder(order.status)) throw new Error('当前订单状态不允许编辑') await updateMerchantFirstLegOrder(id, savePayload(draft, warehouse, customerName)) return loadLegacyDetail(id) } const createdId = await createMerchantFirstLegOrder(savePayload(draft, warehouse, customerName)) return loadLegacyDetail(String(createdId)) } async function saveDraftBatch( drafts: Array<{ clientRowId: string; draft: FirstLegOrderDraft }>, customerName: string, idempotencyKey: string, ): Promise { const items = drafts.map(({ clientRowId, draft }) => { const warehouse = prepareDraftForSave(draft, warehouses.value) return { clientRowId, draft: savePayload(draft, warehouse, customerName) } }) return createMerchantFirstLegOrderBatch(items, idempotencyKey) } async function submitSaved(id: string, companyAuditStatus?: MerchantTenantStatus) { if (companyAuditStatus && companyAuditStatus !== 'ACTIVE') throw new Error('企业资料尚未审核通过,请先完善企业资料并等待平台审核') const idempotencyKey = pendingSubmitKeys.get(id) ?? createIdempotencyKey(`first-leg-submit-${id}`) pendingSubmitKeys.set(id, idempotencyKey) await submitMerchantFirstLegOrder(id, idempotencyKey) pendingSubmitKeys.delete(id) return loadUnifiedDetail(id) } async function submit(draft: FirstLegOrderDraft, customerName: string, id?: string, companyAuditStatus?: MerchantTenantStatus) { const order = await saveDraft(draft, customerName, id) return submitSaved(order.id, companyAuditStatus) } async function loadDraftDetail(id: string) { return loadLegacyDetail(id) } return { orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, draftLoadError, activeCount, pendingCount, getById, load, loadDetail, loadDraftDetail, loadWarehouseDetail, loadWarehouseType, saveDraft, saveDraftBatch, submitSaved, submit } })