fix(first-leg): preserve edit commands and add versioned cancellation

This commit is contained in:
2026-10-02 21:36:10 +08:00
parent c06c105836
commit dae119a1e8
6 changed files with 321 additions and 20 deletions
+55 -8
View File
@@ -10,6 +10,7 @@ import {
getMerchantWarehouses,
submitMerchantFirstLegOrder,
updateMerchantFirstLegOrder,
cancelMerchantFirstLegOrder,
type ApiFirstLegStatus,
type FirstLegOrderSaveRequest,
type MerchantFirstLegOrder,
@@ -468,9 +469,21 @@ function mapWarehouse(item: MerchantWarehouse): ClientWarehouse {
}
}
export interface DraftEditSession {
readonly orderId: string
readonly expectedVersion: number
}
export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', () => {
const orders = ref<FirstLegOrder[]>([])
const pendingSubmitKeys = new Map<string, 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 } }>()
function beginDraftEdit(detail: FirstLegOrder): DraftEditSession {
if (!canClientEditFirstLegOrder(detail.status) || !Number.isSafeInteger(detail.version) || detail.version < 0) throw new Error('请重新打开草稿详情后编辑')
return Object.freeze({ orderId: detail.id, expectedVersion: detail.version })
}
const warehouses = ref<ClientWarehouse[]>([])
const loading = ref(false)
const loadedFromApi = ref(false)
@@ -588,18 +601,52 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
return mapped
}
async function saveDraft(draft: FirstLegOrderDraft, customerName: string, id?: string) {
async function saveDraft(draft: FirstLegOrderDraft, customerName: string, id?: string, editSession?: DraftEditSession) {
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)
if (!editSession || editSession.orderId !== id) throw new Error('请重新打开草稿详情后编辑')
const payload = { expectedVersion: editSession.expectedVersion, draft: savePayload(draft, warehouse, customerName) }
let pending = pendingEdits.get(editSession)
if (pending && JSON.stringify(pending.payload) !== JSON.stringify(payload)) throw new Error('上次保存结果未确认,请重试原内容,或重新打开详情后编辑')
// Observe an uncertain previous outcome without replacing its frozen version.
if (pending) await getMerchantFirstLegOrder(id)
if (!pending) {
pending = { key: createIdempotencyKey(`first-leg-edit-${id}`), payload: JSON.parse(JSON.stringify(payload)) }
pendingEdits.set(editSession, pending)
}
await updateMerchantFirstLegOrder(id, pending.payload, pending.key)
const saved = await loadLegacyDetail(id)
pendingEdits.delete(editSession)
return saved
}
const createdId = await createMerchantFirstLegOrder(savePayload(draft, warehouse, customerName))
return loadLegacyDetail(String(createdId))
}
async function cancelOrder(id: string, expectedVersion: number, reason: string) {
if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0 || !reason.trim() || reason.trim().length > 500) throw new Error('取消订单需要详情版本及 1–500 字取消原因')
const payload = { expectedVersion, reason: reason.trim() }
let pending = pendingCancellations.get(id)
if (pending && JSON.stringify(pending.payload) !== JSON.stringify(payload)) throw new Error('上次取消结果未确认,请使用原版本和原因重试')
if (pending) await getMerchantFirstLegOrder(id)
if (!pending) {
pending = { key: createIdempotencyKey(`first-leg-cancel-${id}`), payload }
pendingCancellations.set(id, pending)
}
await cancelMerchantFirstLegOrder(id, pending.payload, pending.key)
pendingCancellations.delete(id)
orders.value = orders.value.filter(order => order.id !== id)
}
async function prepareCancellation(id: string) {
const { order } = await getMerchantFirstLegOrder(id)
const labels: Record<string, string> = { DRAFT: '草稿', SUBMITTED: '审核中', APPROVED: '已审核', ACCEPTED: '已接单', REJECTED: '已退回' }
if (!Object.hasOwn(labels, order.status)) throw new Error('当前订单状态不允许取消,请刷新订单查看最新进度')
if (!Number.isSafeInteger(order.version) || order.version < 0) throw new Error('订单版本缺失,请重新加载详情')
pendingCancellations.delete(id)
return { id, expectedVersion: order.version, businessNo: order.orderNo || order.inboundCode || '—', statusLabel: labels[order.status] }
}
async function saveDraftBatch(
drafts: Array<{ clientRowId: string; draft: FirstLegOrderDraft }>,
customerName: string,
@@ -621,8 +668,8 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
return loadUnifiedDetail(id)
}
async function submit(draft: FirstLegOrderDraft, customerName: string, id?: string, companyAuditStatus?: MerchantTenantStatus) {
const order = await saveDraft(draft, customerName, 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)
}
@@ -630,5 +677,5 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
return loadLegacyDetail(id, getById(id))
}
return { orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, draftLoadError, activeCount, pendingCount, getById, load, loadDetail, loadDraftDetail, loadWarehouseDetail, loadWarehouseType, saveDraft, 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, submit }
})