import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import type { DispatchEvidenceKind, DispatchRoute, DispatchRouteCommand } from '../dispatchContracts' import { requireVersion, requireBatchVersions, type AddCargo, type ArrangeDelivery, 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 { normalizeArrangementPreview, normalizeCandidateList, normalizeConvertedDraft, normalizeDispatchCommand, normalizeDispatchDetail, normalizeDispatchDraft, normalizeDispatchItems, normalizeDispatchPage, normalizeEvidenceFile, normalizeLoadingPreview, type ArrangementPreviewWire, 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 const getDispatchItems = (no: string) => get(`${path(no)}/items`).then(normalizeDispatchItems) 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 completeDispatchLoading = (no: string, body: LoadingComplete) => postCommand(`${path(no)}/loading-complete`, versioned(body)) export const departDispatch = (no: string, body: VersionCommand & { remark: string | null }) => postCommand(`${path(no)}/depart`, versioned(body)) export const getDispatchArrangement = (no: string) => get(`${path(no)}/delivery-arrangement`).then(normalizeArrangementPreview) export const transferDispatchToDelivery = (no: string, body: ArrangeDelivery) => postCommand(`${path(no)}/transfer-delivery`, 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 })