import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import type { AppointmentAllocations, AppointmentAllocationCommand } from '../dispatchContracts' import type { MarkDispatchException, DispatchEvidenceKind, DispatchRoute, DispatchRouteCommand } from '../dispatchContracts' import { requireVersion, requireBatchVersions, type AddCargo, type CandidateQuery, type ConvertDraft, type CreateDispatch, type DispatchQuery, type DraftContainer, type DraftSelection, type LoadingComplete, type ReasonCommand, type ReviewCommand, type UpdateDispatch, type VersionCommand, type DispatchBatchAllocation } from '../dispatchContracts' import { normalizeCandidateList, normalizeConvertedDraft, normalizeDispatchCommand, normalizeDispatchDetail, normalizeDispatchDraft, normalizeDispatchItems, normalizeDispatchPage, normalizeEvidenceFile, normalizeLoadingPreview, type CandidateListWire, type CommandResultWire, type ConvertedDraftWire, type DispatchDetailWire, type DispatchDraftWire, type DispatchEvidenceFileWire, type DispatchItemsWire, type DispatchPageWire, type LoadingPreviewWire } from '../dispatchWire' export type { DispatchBatchReference, DispatchBatchAllocation } from '../dispatchContracts' const base = '/platform/v1/last-leg/dispatches' const path = (dispatchNo: string) => `${base}/${encodeURIComponent(dispatchNo)}` async function get(url: string) { return unwrapApiResponse(await http.get, ApiEnvelope>(url)) } async function post(url: string, body: object) { return unwrapApiResponse(await http.post, ApiEnvelope>(url, body)) } const postCommand = (url: string, body: object) => post(url, body).then(normalizeDispatchCommand) const postDraft = (url: string, body: object) => post(url, body).then(normalizeDispatchDraft) function versioned(body: T): T { requireVersion(body.recordVersion); return body } export async function listDispatches(query: DispatchQuery) { return normalizeDispatchPage(unwrapApiResponse(await http.get, ApiEnvelope>(base, { params: query }))) } export const getDispatch = (dispatchNo: string) => get(path(dispatchNo)).then(normalizeDispatchDetail) /** 散板余位页只选择已实收打托且与本车同仓的散板来源。 */ export async function listLooseboardCargo(no: string, page = 1, size = 20) { return normalizeCandidateList(unwrapApiResponse(await http.get, ApiEnvelope>(`${base}/capacity-demands/${encodeURIComponent(no)}/cargo`, { params: { page, size } }))) } export const addLooseboardCargo = (no: string, body: AddCargo) => { versioned(body); requireBatchVersions(body.batchAllocations); return postCommand(`${base}/capacity-demands/${encodeURIComponent(no)}/cargo`, body) } export const getDispatchItems = (no: string) => get(`${path(no)}/items`).then(normalizeDispatchItems) export async function getDispatchAppointmentAllocations(no: string) { const value = await get(`${path(no)}/appointment-allocations`) requireVersion(value.recordVersion) value.items.forEach(item => { requireBatchVersions(item.allocations); item.sources.forEach(source => source.options.forEach(option => requireVersion(option.recordVersion))) }) return value } export const saveDispatchAppointmentAllocations = (no: string, body: AppointmentAllocationCommand) => { versioned(body); requireBatchVersions(body.batchAllocations) return postAppointmentAllocations(no, body) } async function postAppointmentAllocations(no: string, body: AppointmentAllocationCommand) { return normalizeDispatchCommand(unwrapApiResponse(await http.put, ApiEnvelope>(`${path(no)}/appointment-allocations`, body))) } export const getDispatchRoute = (no: string) => get(`${path(no)}/route`) export async function reorderDispatchRoute(no: string, body: DispatchRouteCommand) { return normalizeDispatchCommand(unwrapApiResponse(await http.put, ApiEnvelope>(`${path(no)}/route`, versioned(body)))) } const evidencePath = (no: string, kind: DispatchEvidenceKind, id?: string) => `${path(no)}/evidence/${kind}${id ? `/${encodeURIComponent(id)}` : ''}` export async function uploadDispatchEvidence(no: string, kind: DispatchEvidenceKind, file: File) { const form = new FormData(); form.append('file', file); return post(evidencePath(no, kind), form).then(normalizeEvidenceFile) } export const getDispatchEvidence = (no: string, kind: DispatchEvidenceKind, id: string) => get(evidencePath(no, kind, id)).then(normalizeEvidenceFile) export const getDispatchEvidenceContent = (no: string, kind: DispatchEvidenceKind, id: string) => http.get(`${evidencePath(no, kind, id)}/content`, { responseType: 'blob' }) export const getDispatchLoading = (dispatchNo: string) => get(`${path(dispatchNo)}/loading`).then(normalizeLoadingPreview) export const getDispatchCommand = (dispatchNo: string, key: string) => get(`${path(dispatchNo)}/commands/${encodeURIComponent(key)}`).then(normalizeDispatchCommand) export const createDispatch = (body: CreateDispatch) => { requireBatchVersions(body.batchAllocations); return postCommand(base, body) } export async function updateDispatch(dispatchNo: string, body: UpdateDispatch) { versioned(body) return normalizeDispatchCommand(unwrapApiResponse(await http.put, ApiEnvelope>(path(dispatchNo), body))) } export const addDispatchCargo = (no: string, body: AddCargo) => { versioned(body); requireBatchVersions(body.batchAllocations); return postCommand(`${path(no)}/items`, body) } export const removeDispatchItem = (no: string, itemId: string, body: ReasonCommand) => postCommand(`${path(no)}/items/${encodeURIComponent(itemId)}/remove`, versioned(body)) export const cancelDispatch = (no: string, body: ReasonCommand) => postCommand(`${path(no)}/cancel`, versioned(body)) export const submitDispatchReview = (no: string, body: VersionCommand & { remark: string | null }) => postCommand(`${path(no)}/submit-review`, versioned(body)) export const reviewDispatch = (no: string, body: ReviewCommand) => postCommand(`${path(no)}/dispatch-review`, versioned(body)) export const reviewDispatchLoading = (no: string, body: ReviewCommand) => postCommand(`${path(no)}/loading-review`, versioned(body)) export const returnDispatchToReview = (no: string, body: ReasonCommand) => postCommand(`${path(no)}/return-to-review`, versioned(body)) export const markDispatchException = (no: string, body: MarkDispatchException) => postCommand(`${path(no)}/exceptions`, versioned(body)) export const completeDispatchLoading = (no: string, body: LoadingComplete) => postCommand(`${path(no)}/loading-complete`, versioned(body)) export const getDispatchBolPdf = (no: string) => http.get(`${path(no)}/prints/bol`, { responseType: 'blob' }) export async function listDispatchCandidates(query: CandidateQuery) { return normalizeCandidateList(unwrapApiResponse(await http.get, ApiEnvelope>(`${base}/candidates`, { params: query, paramsSerializer: { indexes: null } }))) } export const listDispatchDrafts = () => get(`${base}/drafts`).then(values => values.map(normalizeDispatchDraft)) export const createDispatchDraft = (body: { idempotencyKey: string; warehouseId: string }) => postDraft(`${base}/drafts`, body) export const createDispatchDraftContainer = (body: VersionCommand & { draftToken: string; capacity: number }) => postDraft(`${base}/drafts/containers`, versioned(body)) export const saveDispatchDraftSnapshot = (body: VersionCommand & { draftToken: string; planIds: string[]; containers: DraftContainer[] }) => postDraft(`${base}/drafts/snapshot`, versioned(body)) export const lockDispatchDraftPallet = (body: DraftSelection) => postDraft(`${base}/draft-locks/lock`, versioned(body)) export const unlockDispatchDraftPallet = (body: DraftSelection) => postDraft(`${base}/draft-locks/unlock`, versioned(body)) export const releaseDispatchDraft = (body: VersionCommand & { draftToken: string }) => postDraft(`${base}/draft-locks/release`, versioned(body)) export async function deleteDispatchDraftContainer(token: string, id: string, body: ReasonCommand) { return normalizeDispatchDraft(unwrapApiResponse(await http.delete, ApiEnvelope>(`${base}/drafts/${encodeURIComponent(token)}/containers/${encodeURIComponent(id)}`, { data: versioned(body) }))) } export const convertDispatchDraft = (body: ConvertDraft) => { versioned(body); requireBatchVersions(body.groups.flatMap(group => group.dispatch.batchAllocations)); return post(`${base}/drafts/pre-arrange`, body).then(normalizeConvertedDraft) } // Existing appointment-domain provider; its consumer handoff remains with the coordinator. export interface BatchDispatchCandidate { planPalletId: string; palletNo: string; planId: string; planNo: string; customerId: string; sourceStage: string; packageCount: number | null; allocations: DispatchBatchAllocation[]; blockReasons: string[] } export interface BatchDispatchCandidates { batchKey: string; version: number; candidates: BatchDispatchCandidate[]; blockReasons: string[] } export const getBatchDispatchCandidates = (key: string) => get(`/platform/v1/last-leg/appointment-planning/batches/${encodeURIComponent(key)}/dispatch-candidates`).then(value => { requireBatchVersions(value.candidates.flatMap(row => row.allocations)); return value }) /** 退回货物仅使用当前排车服务端给出的原出库候选。 */ export const getDispatchReturnOptions = (no: string) => get( `${path(no)}/exception-return-options`)