294 lines
15 KiB
TypeScript
294 lines
15 KiB
TypeScript
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'
|
|
|
|
export type ClientWarehouseType = 'DOMESTIC' | 'FBA' | 'OVERSEAS'
|
|
export type ClientWarehouse = PlatformReceivingWarehouse & {
|
|
warehouseType: ClientWarehouseType
|
|
countryCode: string
|
|
}
|
|
|
|
const countryCodes: Readonly<Record<string, string>> = {
|
|
中国: '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: 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,
|
|
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.destinationType === 'FBA' ? draft.amazonWarehouseCode || undefined : undefined,
|
|
shipmentId: draft.destinationType === 'FBA' ? draft.shipmentId || undefined : 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,
|
|
destinationWarehouseId: order.destinationWarehouseId ? String(order.destinationWarehouseId) : '',
|
|
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): 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<FirstLegOrder[]>([])
|
|
const warehouses = ref<ClientWarehouse[]>([])
|
|
const loading = ref(false)
|
|
const loadedFromApi = ref(false)
|
|
const loadError = ref('')
|
|
const warehouseLoadError = 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 = ''
|
|
warehouseLoadError.value = ''
|
|
orders.value = []
|
|
warehouses.value = []
|
|
const [orderResult, warehouseResult] = await Promise.allSettled([getMerchantFirstLegOrders(), getMerchantWarehouses()])
|
|
if (warehouseResult.status === 'fulfilled') {
|
|
warehouses.value = warehouseResult.value.records
|
|
.filter((item) => item.status === 'ACTIVE' && ['DOMESTIC', 'FBA', 'OVERSEAS'].includes(item.warehouseType))
|
|
.map(mapWarehouse)
|
|
} else {
|
|
warehouseLoadError.value = warehouseResult.reason instanceof Error ? warehouseResult.reason.message : '公共仓库数据加载失败'
|
|
}
|
|
if (orderResult.status === 'fulfilled') {
|
|
orders.value = orderResult.value.records.map((item) => mapOrder(item, warehouses.value))
|
|
} else {
|
|
loadError.value = orderResult.reason instanceof Error ? orderResult.reason.message : '订单数据加载失败'
|
|
}
|
|
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 || warehouse.warehouseType !== 'DOMESTIC') throw new Error('所选国内收货仓当前不可用')
|
|
if (draft.destinationType !== 'PRIVATE') {
|
|
const destinationWarehouse = warehouses.value.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 = ''
|
|
}
|
|
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) {
|
|
if (companyAuditStatus && 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, warehouseLoadError, activeCount, pendingCount, getById, load, loadDetail, loadWarehouseDetail, saveDraft, submit }
|
|
})
|