import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { createMerchantFirstLegOrder, getMerchantFirstLegOrder, getMerchantFirstLegOrders, getMerchantWarehouseDetail, getMerchantWarehouses, submitMerchantFirstLegOrder, updateMerchantFirstLegOrder, type ApiFirstLegStatus, type FirstLegOrderPartyPayload, type FirstLegOrderSaveRequest, type MerchantFirstLegOrder, type MerchantFirstLegOrderDetail, type MerchantWarehouse, } from '@/modules/first-leg/api/firstLeg' 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 } from '@/modules/first-leg/utils/firstLegOrder' const countryCodes: Readonly> = { 中国: 'CN', 美国: 'US', 德国: 'DE', 英国: 'GB', 加拿大: 'CA', 日本: 'JP', 澳大利亚: 'AU', } function clientStatus(status: ApiFirstLegStatus): FirstLegOrder['status'] { if (status === 'SUBMITTED') return 'PENDING_REVIEW' // 后端联调任务曾出现 REJECTED,但一期已确认订单不设驳回状态;兼容旧数据时仍按待审核展示。 if (status === 'REJECTED') return 'PENDING_REVIEW' if (status === 'APPROVED' || status === 'ACCEPTED') return 'ACCEPTED' if (status === 'PROCESSING') return 'IN_TRANSIT' return status } 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: PlatformReceivingWarehouse, customerName: string): FirstLegOrderSaveRequest { if (!draft.shipperPartySnapshot || !draft.consigneePartySnapshot) throw new Error('请完整填写收发货信息') return { // 后端把 customerId 作为订单主往来单位校验并以服务端名称覆盖 customerName; // 当前商户下单以所选发货单位作为主往来单位,收发货详情仍分别保存订单快照。 customerId: draft.shipperPartyId, customerName, transportMode: apiTransport(draft), tradeTerm: draft.tradeTerm || undefined, destinationCountryCode: draft.destinationCountryCode.trim().toUpperCase(), destinationWarehouseName: draft.destinationWarehouseName, expectedShipDate: draft.expectedDepartureDate || undefined, customerRemark: draft.customerRemark || undefined, sender: partyPayload(draft.shipperPartySnapshot), receiver: partyPayload(draft.consigneePartySnapshot), cargoes: draft.items.map((item) => ({ cargoName: item.cargoNameCn, cargoNameEn: item.cargoNameEn || undefined, hsCode: item.hsCode || undefined, packageType: item.packageType, packageCount: item.packageCount, grossWeightKg: item.grossWeightKg, volumeCbm: item.volumeCbm, 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, })), pickupMethod: draft.pickupMethod, receivingWarehouseId: warehouse.id, receivingWarehouseName: warehouse.name, channelCode: draft.channelCode, channelName: draft.channelName, taxMode: draft.taxMode, containerMode: draft.transportMode === 'FCL' ? 'FULL_CONTAINER' : draft.transportMode === 'LCL' ? 'LCL' : undefined, customsInspection: draft.customsInspection, destinationType: draft.destinationType, amazonWarehouseCode: draft.amazonWarehouseCode || undefined, shipmentId: draft.shipmentId || undefined, referenceId: draft.referenceNo || undefined, } } 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) => ({ id: String(item.id), event: item.toStatus === 'SUBMITTED' || item.toStatus === 'REJECTED' ? 'SUBMITTED' : item.toStatus === 'CANCELLED' ? 'CANCELLED' : item.toStatus === 'COMPLETED' ? 'COMPLETED' : item.toStatus === 'ACCEPTED' || item.toStatus === 'APPROVED' ? 'ACCEPTED' : 'IN_TRANSIT', title: item.toStatus === 'REJECTED' ? '平台要求补充订单信息' : item.action || `状态更新为 ${item.toStatus}`, description: item.reason ?? '', operatorName: item.operatorName ?? '', occurredAt: item.occurredAt, })) } 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), orderNo: order.orderNo, referenceNo: order.referenceId ?? '', transportMode: clientTransport(order), 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 ?? '', 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 ?? '', pieces: item.totalQuantity ?? item.packageCount, packageCount: item.packageCount, grossWeightKg: item.grossWeightKg, volumeCbm: item.volumeCbm, containerType: '', containerCount: 0, remark: item.cargoRemark ?? '', packageType: item.packageType, hsCode: item.hsCode ?? '', declaredValue: item.declaredValue ?? 0, currency: item.currency ?? 'USD', dangerousGoods: item.dangerousGoods ?? false, })), timeline: detail ? timeline(detail) : [], createdAt: order.createdAt, updatedAt: order.updatedAt, tradeTerm: order.tradeTerm ?? '', destinationCountryCode: order.destinationCountryCode, destinationWarehouseName: order.destinationWarehouseName, pickupMethod: order.pickupMethod, channelCode: order.channelCode, channelName: order.channelName, taxMode: order.taxMode, customsInspection: order.customsInspection, destinationType: order.destinationType, amazonWarehouseCode: order.amazonWarehouseCode ?? '', shipmentId: order.shipmentId ?? '', } } function mapWarehouse(item: MerchantWarehouse): PlatformReceivingWarehouse { 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 ?? '', } } export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () => { const orders = ref([]) const warehouses = ref([]) const loading = ref(false) const loadedFromApi = ref(false) const loadError = ref('') const activeCount = computed(() => orders.value.filter((item) => ['PENDING_REVIEW', 'ACCEPTED', 'BOOKED', 'IN_TRANSIT'].includes(item.status)).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 load() { loading.value = true loadedFromApi.value = true loadError.value = '' orders.value = [] warehouses.value = [] try { const [orderPage, warehousePage] = await Promise.all([getMerchantFirstLegOrders(), getMerchantWarehouses()]) warehouses.value = warehousePage.records.filter((item) => item.status === 'ACTIVE').map(mapWarehouse) orders.value = orderPage.records.map((item) => mapOrder(item, warehouses.value)) } catch (error) { loadError.value = error instanceof Error ? error.message : '订单与仓库数据加载失败' throw error } finally { loading.value = false } } async function loadDetail(id: string) { const detail = await getMerchantFirstLegOrder(id) const mapped = mapOrder(detail.order, warehouses.value, detail) const index = orders.value.findIndex((item) => item.id === id) if (index >= 0) orders.value[index] = mapped else orders.value.unshift(mapped) return mapped } 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 = warehouses.value.find((item) => item.id === draft.receivingWarehouseId) if (!warehouse) throw new Error('所选收货仓当前不可用') if (id) { const order = getById(id) if (!order || !canClientEditFirstLegOrder(order.status)) throw new Error('当前订单状态不允许编辑') await updateMerchantFirstLegOrder(id, savePayload(draft, warehouse, customerName)) return loadDetail(id) } const createdId = await createMerchantFirstLegOrder(savePayload(draft, warehouse, customerName)) return loadDetail(String(createdId)) } async function submit(draft: FirstLegOrderDraft, customerName: string, id?: string, companyAuditStatus: MerchantTenantStatus = 'ACTIVE') { if (companyAuditStatus !== 'ACTIVE') throw new Error('企业资料尚未审核通过,请先完善企业资料并等待平台审核') const order = await saveDraft(draft, customerName, id) await submitMerchantFirstLegOrder(order.id) return loadDetail(order.id) } return { orders, warehouses, loading, loadedFromApi, loadError, activeCount, pendingCount, getById, load, loadDetail, loadWarehouseDetail, saveDraft, submit } })