fix(first-leg): freeze versioned submission commands across retries

This commit is contained in:
2026-10-02 23:08:58 +08:00
parent 187deb5047
commit e564353a80
6 changed files with 251 additions and 30 deletions
+52 -11
View File
@@ -384,7 +384,8 @@ function mapUnifiedOrderDetail(detail: MerchantUnifiedOrderDetail, warehouses: P
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.order.order.status === 'CANCELLED' ? 'CANCELLED' : detail.summary.mainStatus
const rawStatus = detail.order.order.status
mapped.status = rawStatus === 'CANCELLED' || rawStatus === 'DRAFT' ? rawStatus : detail.summary.mainStatus
mapped.fulfillmentType = detail.summary.fulfillmentType ?? mapped.fulfillmentType
mapped.lxCode = detail.summary.lxCode ?? mapped.lxCode
mapped.businessSummary = businessSummary(detail.summary)
@@ -476,7 +477,9 @@ export interface DraftEditSession {
export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () => {
const orders = ref<FirstLegOrder[]>([])
const pendingSubmitKeys = new Map<string, string>()
const pendingSubmissions = new Map<string, { key: string; payload: { expectedVersion: number } }>()
const submissionForms = new Map<string, { snapshot: string; order: FirstLegOrder }>()
const submittingIds = new Set<string>()
const pendingEdits = new WeakMap<DraftEditSession, { key: string; payload: { expectedVersion: number; draft: FirstLegOrderSaveRequest } }>()
const pendingCancellations = new Map<string, { key: string; payload: { expectedVersion: number; reason: string } }>()
@@ -602,6 +605,7 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
}
async function saveDraft(draft: FirstLegOrderDraft, customerName: string, id?: string, editSession?: DraftEditSession) {
if (hasPendingSubmission(id)) throw new Error('上次提交结果未确认,请重试原提交,或确认重新编辑后再保存')
const warehouse = prepareDraftForSave(draft, warehouses.value)
if (id) {
if (!editSession || editSession.orderId !== id) throw new Error('请重新打开草稿详情后编辑')
@@ -661,21 +665,58 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
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)
if (submittingIds.has(id)) throw new Error('订单正在提交,请稍候')
submittingIds.add(id)
try {
let pending = pendingSubmissions.get(id)
// Always observe the raw detail; a retry must retain the original command.
const { order } = await getMerchantFirstLegOrder(id)
if (!pending) {
if (order.status !== 'DRAFT') throw new Error('当前订单不是草稿,请刷新订单查看最新状态')
if (!Number.isSafeInteger(order.version) || order.version < 0) throw new Error('订单版本缺失,请重新加载详情')
pending = { key: createIdempotencyKey(`first-leg-submit-${id}`), payload: { expectedVersion: order.version } }
pendingSubmissions.set(id, pending)
}
await submitMerchantFirstLegOrder(id, pending.payload, pending.key)
const submitted = await loadLegacyDetail(id)
pendingSubmissions.delete(id)
submissionForms.delete(id)
return submitted
} finally {
submittingIds.delete(id)
}
}
async function submit(draft: FirstLegOrderDraft, customerName: string, id?: string, companyAuditStatus?: MerchantTenantStatus, editSession?: DraftEditSession) {
const order = await saveDraft(draft, customerName, id, editSession)
return submitSaved(order.id, companyAuditStatus)
function hasPendingSubmission(id?: string) { return !!id && (pendingSubmissions.has(id) || submissionForms.has(id)) }
function isSubmissionRetry(id: string | undefined, draft: FirstLegOrderDraft) {
return !!id && hasPendingSubmission(id) && submissionForms.get(id)?.snapshot === JSON.stringify(draft)
}
async function beginNewSubmissionEdit(id: string) {
if (submittingIds.has(id)) throw new Error('订单正在提交,请稍候')
const detail = await getMerchantFirstLegOrder(id)
if (detail.order.status !== 'DRAFT') throw new Error('订单已不在草稿状态,不能重新编辑,请刷新订单查看最新状态')
if (!Number.isSafeInteger(detail.order.version) || detail.order.version < 0) throw new Error('订单版本缺失,请重新加载详情')
const order = mapOrder(detail.order, warehouses.value, detail)
const session = beginDraftEdit(order)
pendingSubmissions.delete(id)
submissionForms.delete(id)
upsertOrder(order)
return session
}
async function saveDraftForSubmission(draft: FirstLegOrderDraft, customerName: string, id?: string, editSession?: DraftEditSession) {
if (id && isSubmissionRetry(id, draft)) return submissionForms.get(id)!.order
const snapshot = JSON.stringify(draft)
const order = await saveDraft(JSON.parse(snapshot), customerName, id, editSession)
submissionForms.set(order.id, { snapshot, order })
return order
}
async function loadDraftDetail(id: string) {
return loadLegacyDetail(id, getById(id))
}
return { orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, draftLoadError, activeCount, pendingCount, getById, load, loadDetail, loadDraftDetail, loadWarehouseDetail, loadWarehouseType, beginDraftEdit, saveDraft, prepareCancellation, cancelOrder, saveDraftBatch, submitSaved, submit }
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 }
})