Files
admin-web/src/modules/overseas-warehouse/useDispatchCandidates.ts
T

61 lines
3.7 KiB
TypeScript

import { ref, type Ref } from 'vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { listDispatchCandidates } from './api/dispatches'
import { DispatchInputError, assertSelectionGroupVersions, type Candidate, type CandidateQuery } from './dispatchContracts'
/** One visible server page; selected cargo remains owned by the caller or server draft. */
export function useDispatchCandidates(error: Ref<ApiErrorDetails | null>, fetchCandidates = listDispatchCandidates) {
const candidates = ref<Candidate[]>([]), page = ref(1), size = ref(20), total = ref(0), loading = ref(false)
let generation = 0, warehouse = ''
let pending: { key: string; promise: Promise<void> } | undefined
const groupVersions = new Map<string, string>()
const sourceGroups = new Map<string, { groupKey: string; sourceVersion: string }>()
function reset() {
generation++; pending = undefined; warehouse = ''
candidates.value = []; page.value = 1; total.value = 0; loading.value = false
groupVersions.clear(); sourceGroups.clear()
}
function assertCurrent(rows: Candidate[]) {
assertSelectionGroupVersions(rows, candidates.value)
for (const row of rows) {
const group = row.selectionGroup
if (row.warehouseId !== warehouse || (groupVersions.has(group.groupKey) && groupVersions.get(group.groupKey) !== group.groupVersion)
|| group.sources.some(source => {
const latest = sourceGroups.get(source.sourceKey)
return latest && (latest.groupKey !== group.groupKey || latest.sourceVersion !== source.selection.sourceVersion)
})) throw new DispatchInputError('选货组组成已变化,请重新读取并选择货物')
}
}
function load(query: CandidateQuery): Promise<void> {
if (!query.warehouseId) { reset(); return Promise.resolve() }
if (warehouse !== query.warehouseId) { reset(); warehouse = query.warehouseId }
const request = { ...query, page: query.page ?? page.value, size: query.size ?? size.value, warehouseTab: query.warehouseTab ?? 'ALL' }
const key = JSON.stringify(request)
if (pending?.key === key) return pending.promise
const sequence = ++generation
// Preserve the last total while loading: zero would make the pager emit page 1.
loading.value = true; error.value = null; candidates.value = []
const promise = (async () => {
try {
const result = await fetchCandidates(request)
if (sequence !== generation) return
if (result.page !== request.page || result.size !== request.size) throw new DispatchInputError('候选响应页码不一致,请重新读取')
if (result.candidates.some(row => row.warehouseId !== request.warehouseId || !row.sourceVersion || row.selection.sourceVersion !== row.sourceVersion)) throw new DispatchInputError('候选起运仓或来源版本不完整,请重新读取')
for (const row of result.candidates) {
groupVersions.set(row.selectionGroup.groupKey, row.selectionGroup.groupVersion)
for (const source of row.selectionGroup.sources) sourceGroups.set(source.sourceKey, { groupKey: row.selectionGroup.groupKey, sourceVersion: source.selection.sourceVersion })
}
candidates.value = result.candidates; page.value = result.page; size.value = result.size; total.value = result.total
} catch (cause) {
if (sequence === generation) error.value = getApiErrorDetails(cause, cause instanceof DispatchInputError ? cause.message : '货物候选读取失败')
} finally {
if (sequence === generation) { loading.value = false; pending = undefined }
}
})()
pending = { key, promise }
return promise
}
return { candidates, page, size, total, loading, load, reset, assertCurrent }
}