fix(client): request complete server-paged order workbench

This commit is contained in:
2026-10-03 14:57:54 +08:00
parent 78315873d7
commit 40f6753cca
10 changed files with 329 additions and 284 deletions
+49 -141
View File
@@ -1,11 +1,10 @@
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { defineStore } from 'pinia'
import {
createMerchantFirstLegOrderBatch,
createMerchantFirstLegOrder,
createIdempotencyKey,
getMerchantFirstLegOrder,
getMerchantFirstLegOrders,
getMerchantWarehouseDetail,
getMerchantWarehouses,
submitMerchantFirstLegOrder,
@@ -14,17 +13,15 @@ import {
type ApiFirstLegStatus,
type FirstLegOrderSaveRequest,
type MerchantFirstLegOrder,
type MerchantFirstLegOrderRow,
type MerchantFirstLegOrderDetail,
type MerchantBatchDraftResponse,
type MerchantWarehouse,
} from '@/modules/first-leg/api/firstLeg'
import {
getMerchantUnifiedOrder,
getMerchantUnifiedOrders,
isUnifiedOrderQueryUnavailable,
getMerchantOrderWorkbench,
type MerchantUnifiedOrderDetail,
type MerchantUnifiedOrderQuery,
type MerchantOrderWorkbenchQuery,
type MerchantUnifiedOrderRow,
} from '@/modules/first-leg/api/unifiedOrders'
import type { MerchantTenantStatus } from '@/modules/tenant-management/public'
@@ -342,7 +339,7 @@ function mapUnifiedOrderRow(summary: MerchantUnifiedOrderRow, warehouses: Platfo
trackingNumber: summary.expressTrackingNo ?? summary.delivery?.trackingNo ?? '', skuNos: summary.skuNos ?? [], totalNetWeightKg: summary.totalNetWeightKg ?? null,
totalQuantity: summary.totalQuantity ?? null, chargeableVolumeCbm: summary.chargeableVolumeCbm ?? null,
inboundedAt: summary.inboundCompletedAt ?? '', businessDate: '', priority: 'NORMAL', customerRemark: summary.customerRemark ?? '', operatorName: '',
status: summary.mainStatus, version: 0, items: [], timeline: [], businessSummary: businessSummary(summary),
status: summary.mainStatus, version: summary.orderVersion, items: [], timeline: [], businessSummary: businessSummary(summary),
createdAt: summary.createdAt, updatedAt: summary.updatedAt, tradeTerm: '', destinationCountryCode: '',
destinationWarehouseId: summary.destinationWarehouseId == null ? '' : String(summary.destinationWarehouseId),
destinationWarehouseName: summary.destinationWarehouseName ?? '', pickupMethod: summary.pickupMethod ?? 'SELF_EXPRESS', channelCode: '',
@@ -352,36 +349,6 @@ function mapUnifiedOrderRow(summary: MerchantUnifiedOrderRow, warehouses: Platfo
}
}
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', fulfillmentType: row.fulfillmentType ?? 'STANDARD', lxCode: row.lxCode ?? '', 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: row.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 ?? '', requestedPickupAt: '', 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)
@@ -410,59 +377,6 @@ function mapUnifiedOrderDetail(detail: MerchantUnifiedOrderDetail, warehouses: P
return mapped
}
type OrderListReadResult =
| { source: 'first-leg'; records: MerchantFirstLegOrderRow[] }
| { source: 'unified'; records: MerchantUnifiedOrderRow[]; drafts: MerchantFirstLegOrderRow[] }
interface ClientOrderReadQuery extends MerchantUnifiedOrderQuery {
fbaCode?: string
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<OrderListReadResult> {
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,
inboundCode: query.inboundCode,
createdFrom: query.createdFrom,
createdTo: query.createdTo,
fbaCode: query.fbaCode,
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: '',
@@ -479,6 +393,11 @@ export interface DraftEditSession {
export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () => {
const orders = ref<FirstLegOrder[]>([])
const orderDetails = ref<Record<string, FirstLegOrder>>({})
const total = ref(0)
const queueCounts = ref({ all: 0, fee: 0, customs: 0, draft: 0 })
const queryChannelNames = ref<string[]>([])
let listRequest = 0
const pendingSubmissions = new Map<string, { key: string; payload: { expectedVersion: number } }>()
const submissionForms = new Map<string, { snapshot: string; order: FirstLegOrder }>()
const submittingIds = new Set<string>()
@@ -494,13 +413,10 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
const loadedFromApi = ref(false)
const loadError = ref('')
const warehouseLoadError = ref('')
const draftLoadError = ref('')
const loadedWarehouseTypes = ref<ClientWarehouseType[]>([])
const warehouseTypeRequests = new Map<ClientWarehouseType, Promise<ClientWarehouse[]>>()
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) }
function getById(id: string) { return orders.value.find((item) => item.id === id) ?? orderDetails.value[id] }
async function loadWarehouseType(warehouseType: ClientWarehouseType, force = false) {
if (warehouseType === 'DOMESTIC' && force) warehouseLoadError.value = ''
@@ -527,28 +443,40 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
}
}
async function load(query: ClientOrderReadQuery = {}) {
async function load(query: MerchantOrderWorkbenchQuery = {}) {
const request = ++listRequest
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 : '公共仓库数据加载失败'
total.value = 0
queueCounts.value = { all: 0, fee: 0, customs: 0, draft: 0 }
const [orderResult, warehouseResult] = await Promise.allSettled([
getMerchantOrderWorkbench(query), loadWarehouseType('DOMESTIC'),
])
if (request !== listRequest) return 0
if (warehouseResult.status === 'rejected') 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 : '订单数据加载失败'
}
try {
const result = orderResult.value
const expectedPage = query.page ?? 1, size = query.size ?? 10
const count = Number(result.page.total), pages = Math.ceil(count / size)
if (!Number.isSafeInteger(count) || count < 0 || Number(result.page.page) !== expectedPage
|| Number(result.page.size) !== size || Number(result.page.pages) !== pages
|| result.page.records.length !== Math.min(size, Math.max(0, count - (expectedPage - 1) * size))
|| new Set(result.page.records.map(row => String(row.orderId))).size !== result.page.records.length
|| ['all', 'fee', 'customs', 'draft'].some(key => { const value = result.queues[key as keyof typeof result.queues]; return !Number.isSafeInteger(Number(value)) || Number(value) < 0 })
|| Number(result.queues[query.queue ?? 'all']) !== count
|| !Array.isArray(result.channelNames)) {
loadError.value = '订单分页数据不完整,请重新加载'
} else {
orders.value = result.page.records.map(item => mapUnifiedOrderRow(item, warehouses.value))
total.value = count
queueCounts.value = { all: Number(result.queues.all), fee: Number(result.queues.fee), customs: Number(result.queues.customs), draft: Number(result.queues.draft) }
queryChannelNames.value = result.channelNames
}
} catch { loadError.value = '订单分页数据不完整,请重新加载' }
} else loadError.value = orderResult.reason instanceof Error ? orderResult.reason.message : '订单数据加载失败'
loading.value = false
return orders.value.length
}
@@ -556,38 +484,18 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
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)
orderDetails.value[mapped.id] = mapped
return mapped
}
async function loadLegacyDetail(id: string, listOrder?: FirstLegOrder) {
async function loadOrderAggregate(id: string) {
const detail = await getMerchantFirstLegOrder(id)
const mapped = mapOrder(detail.order, warehouses.value, detail)
// Older detail responses may omit newly added dropship fields. Keep the
// values already returned by the list row so editing a draft does not
// clear the persisted LX code in the form.
if (detail.order.fulfillmentType == null && listOrder?.fulfillmentType) {
mapped.fulfillmentType = listOrder.fulfillmentType
}
if (detail.order.lxCode == null && listOrder?.lxCode) {
mapped.lxCode = listOrder.lxCode
}
return upsertOrder(mapped)
}
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)
}
return upsertOrder(mapOrder(detail.order, warehouses.value, detail))
}
async function loadDetail(id: string) {
const listOrder = getById(id)
return listOrder?.status === 'DRAFT' ? loadLegacyDetail(id, listOrder) : loadUnifiedDetail(id)
const detail = await getMerchantUnifiedOrder(id)
return upsertOrder(mapUnifiedOrderDetail(detail, warehouses.value))
}
async function loadWarehouseDetail(id: string) {
@@ -621,12 +529,12 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
pendingEdits.set(editSession, pending)
}
await updateMerchantFirstLegOrder(id, pending.payload, pending.key)
const saved = await loadLegacyDetail(id)
const saved = await loadOrderAggregate(id)
pendingEdits.delete(editSession)
return saved
}
const createdId = await createMerchantFirstLegOrder(savePayload(draft, warehouse, customerName))
return loadLegacyDetail(String(createdId))
return loadOrderAggregate(String(createdId))
}
async function cancelOrder(id: string, expectedVersion: number, reason: string) {
@@ -680,7 +588,7 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
pendingSubmissions.set(id, pending)
}
await submitMerchantFirstLegOrder(id, pending.payload, pending.key)
const submitted = await loadLegacyDetail(id)
const submitted = await loadOrderAggregate(id)
pendingSubmissions.delete(id)
submissionForms.delete(id)
return submitted
@@ -717,8 +625,8 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
}
async function loadDraftDetail(id: string) {
return loadLegacyDetail(id, getById(id))
return loadOrderAggregate(id)
}
return { orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, draftLoadError, activeCount, pendingCount, getById, load, loadDetail, loadDraftDetail, loadWarehouseDetail, loadWarehouseType, beginDraftEdit, saveDraft, prepareCancellation, cancelOrder, saveDraftBatch, submitSaved, hasPendingSubmission, isSubmissionRetry, beginNewSubmissionEdit, saveDraftForSubmission }
return { orders, orderDetails, total, queueCounts, queryChannelNames, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, getById, load, loadDetail, loadDraftDetail, loadWarehouseDetail, loadWarehouseType, beginDraftEdit, saveDraft, prepareCancellation, cancelOrder, saveDraftBatch, submitSaved, hasPendingSubmission, isSubmissionRetry, beginNewSubmissionEdit, saveDraftForSubmission }
})