146 lines
10 KiB
TypeScript
146 lines
10 KiB
TypeScript
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
|
import type { FbaAppointmentDetailsPayload, FbaAppointmentDto } from '@/modules/overseas-warehouse'
|
|
|
|
export interface PlanningCargo {
|
|
sourceKey: string
|
|
orderId: string
|
|
customerId: string
|
|
customerName: string | null
|
|
inboundCode: string | null
|
|
containerBookingId: string
|
|
containerNo: string | null
|
|
originWarehouseId: string
|
|
originWarehouseCode: string
|
|
originWarehouseName: string
|
|
destinationWarehouseCode: string
|
|
packages: number | null
|
|
receivedPackages: number | null
|
|
shippedPackages: number | null
|
|
sourcePalletCount: number | null
|
|
volumeCbm: number | null
|
|
estimatedArrivalAt: string | null
|
|
estimatedWarehouseAt: string | null
|
|
orderVersion: number
|
|
standardNode: string
|
|
shippingVersion: string | null
|
|
shippingObservedAt: string | null
|
|
shippingProvider: string | null
|
|
hasInboundPlan: boolean
|
|
hasActiveAppointment: boolean
|
|
detained: boolean
|
|
mixedDestinations: boolean
|
|
}
|
|
export interface PlanningCargoPage { records: PlanningCargo[]; page: number; size: number; total: number; pages: number }
|
|
export async function listPlanningCargo(query: { page: number; size: number; keyword?: string; destination?: string }, signal?: AbortSignal) {
|
|
const response = await http.get<ApiEnvelope<PlanningCargoPage>, ApiEnvelope<PlanningCargoPage>>(
|
|
'/platform/v1/last-leg/appointment-planning/cargo', { params: query, signal },
|
|
)
|
|
const page = unwrapApiResponse(response)
|
|
return { ...page, page: Number(page.page), size: Number(page.size), total: Number(page.total), pages: Number(page.pages) }
|
|
}
|
|
|
|
export async function listPlanningDestinations(signal?: AbortSignal) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<string[]>, ApiEnvelope<string[]>>(
|
|
'/platform/v1/last-leg/appointment-planning/cargo/destinations', { signal },
|
|
))
|
|
}
|
|
|
|
export type FollowupStage = 'DRAYAGE_CONTACT' | 'TERMINAL_APPOINTMENT' | 'WAREHOUSE_RECEIVING' | 'FBA_RECEIVING'
|
|
export interface FollowupRecord { stage: FollowupStage; version: number; contactedParty: string | null; result: string; nextAction: string | null; followupDate: string | null; operatorName: string | null; createdAt: string }
|
|
export interface FollowupHistory { history: { records: FollowupRecord[]; page: number; size: number; total: number; pages: number }; versions: Record<FollowupStage, number> }
|
|
export interface SaveFollowup { stage: FollowupStage; expectedVersion: number; idempotencyKey: string; contactedParty?: string; result: string; nextAction?: string; followupDate: string | null }
|
|
export async function getFollowups(sourceKey: string, customerId: string, page: number, size: number) {
|
|
const result = unwrapApiResponse(await http.get<ApiEnvelope<FollowupHistory>, ApiEnvelope<FollowupHistory>>(
|
|
`/platform/v1/last-leg/appointment-planning/sources/${encodeURIComponent(sourceKey)}/follow-ups`, { params: { customerId, page, size } },
|
|
))
|
|
return { ...result, history: { ...result.history, total: Number(result.history.total) }, versions: Object.fromEntries(Object.entries(result.versions).map(([stage, version]) => [stage, Number(version)])) as Record<FollowupStage, number> }
|
|
}
|
|
export async function saveFollowup(sourceKey: string, customerId: string, body: SaveFollowup) {
|
|
return unwrapApiResponse(await http.post<ApiEnvelope<FollowupRecord>, ApiEnvelope<FollowupRecord>>(
|
|
`/platform/v1/last-leg/appointment-planning/sources/${encodeURIComponent(sourceKey)}/follow-ups`, body, { params: { customerId } },
|
|
))
|
|
}
|
|
|
|
export interface PlanningSource {
|
|
sourceKey: string; customerId: string; orderId: string | null; inboundCode: string | null; bookingNo: string | null;
|
|
containerNo: string | null; originWarehouseId: string; originWarehouseName: string; destinationKey: string;
|
|
destinationWarehouseCode: string; packages: number | null; sourceVersion: string | null; canFollowup: boolean; blockReasons: string[];
|
|
}
|
|
export interface AppointmentCoverage {
|
|
status: string; missingFields: string[]; changeReviewRequired: boolean;
|
|
appointments: Array<{ appointmentNo: string; confirmationNo: string | null; status: string; confirmedAt: string | null; confirmedEndAt: string | null; timezone: string | null }>;
|
|
}
|
|
export interface ResolvedPlanningSource { source: PlanningSource; coverage: AppointmentCoverage }
|
|
export type PlanningSourceLookup = { sourceKey: string; customerId: string; destinationKey?: string } | { orderId: string; customerId: string; destinationKey?: string }
|
|
export async function resolvePlanningOrder(query: { orderId: string; customerId: string; destinationKey?: string }) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<ResolvedPlanningSource>, ApiEnvelope<ResolvedPlanningSource>>(
|
|
'/platform/v1/last-leg/appointment-planning/sources/resolve-order', { params: query },
|
|
))
|
|
}
|
|
export interface ExternalAppointmentCandidate extends ResolvedPlanningSource { customerName: string | null; tripNo: string }
|
|
export async function listExternalAppointmentCandidates(query: { page: number; size: number; keyword?: string }, signal?: AbortSignal) {
|
|
const result = unwrapApiResponse(await http.get<ApiEnvelope<{ records: ExternalAppointmentCandidate[]; total: number }>, ApiEnvelope<{ records: ExternalAppointmentCandidate[]; total: number }>>(
|
|
'/platform/v1/last-leg/appointment-planning/external-candidates', { params: query, signal },
|
|
))
|
|
return { ...result, total: Number(result.total) }
|
|
}
|
|
export interface PlanningBatchPreview {
|
|
group: { originWarehouseId: string; destinationKey: string; packages: number | null; sourceCount: number; customerCount: number; readinessIncomplete: boolean; readyFrom: string | null; readyTo: string | null; unknownQuantitySources: number }
|
|
originWarehouseName: string
|
|
destinationWarehouseCode: string
|
|
destinationTimezone: string | null
|
|
sources: PlanningSource[]
|
|
missingFields: string[]
|
|
}
|
|
export async function previewPlanningBatch(sources: Array<{sourceKey: string; customerId: string; destinationKey: string}>) {
|
|
const result = unwrapApiResponse(await http.post<ApiEnvelope<PlanningBatchPreview>, ApiEnvelope<PlanningBatchPreview>>(
|
|
'/platform/v1/last-leg/appointment-planning/batches/preview', { sources },
|
|
))
|
|
return { ...result, group: { ...result.group, packages: result.group.packages == null ? null : Number(result.group.packages), sourceCount: Number(result.group.sourceCount), customerCount: Number(result.group.customerCount) } }
|
|
}
|
|
export interface SourceAppointmentPayload { originWarehouseId: string; fbaWarehouseCode: string; sources: Array<{sourceKey: string; customerId: string; destinationKey: string}>; details: FbaAppointmentDetailsPayload; idempotencyKey: string }
|
|
export async function createSourceAppointment(payload: SourceAppointmentPayload) {
|
|
return unwrapApiResponse(await http.post<ApiEnvelope<FbaAppointmentDto>, ApiEnvelope<FbaAppointmentDto>>('/platform/v1/last-leg/appointment-planning/appointments', payload))
|
|
}
|
|
export async function listUnscheduledAppointments(page: number, size: number) {
|
|
const result = unwrapApiResponse(await http.get<ApiEnvelope<{records: FbaAppointmentDto[]; total: number}>, ApiEnvelope<{records: FbaAppointmentDto[]; total: number}>>('/platform/v1/last-leg/appointment-planning/appointments/unscheduled', { params: { page, size } }))
|
|
return { ...result, total: Number(result.total) }
|
|
}
|
|
export async function resolvePlanningSource(query: { sourceKey: string; customerId: string; destinationKey?: string }) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<ResolvedPlanningSource>, ApiEnvelope<ResolvedPlanningSource>>(
|
|
'/platform/v1/last-leg/appointment-planning/sources/resolve', { params: query },
|
|
))
|
|
}
|
|
|
|
export interface PlanningContainer {
|
|
impact?: import('./impactApi').Impact | null
|
|
containerBookingId: string; containerNo: string | null; orderCount: number; customerCount: number;
|
|
packages: number | null; unknownPackageOrders: number; earliestArrivalAt: string | null; latestArrivalAt: string | null;
|
|
standardNode: string; sourceVersion: string | null; incompleteScope: boolean;
|
|
warehouses: Array<{ warehouseId: string; warehouseName: string; orderCount: number; customerCount: number }>;
|
|
}
|
|
export interface ContainerFollowupContext {
|
|
containerBookingId: string; containerNo: string | null; warehouseId: string | null; warehouseName: string | null;
|
|
canFollowup: boolean; blockReasons: string[];
|
|
}
|
|
const containerBase = '/platform/v1/last-leg/appointment-planning/containers'
|
|
export async function listPlanningContainers(query: { page: number; size: number; keyword?: string }, signal?: AbortSignal) {
|
|
const result = unwrapApiResponse(await http.get<ApiEnvelope<{ records: PlanningContainer[]; total: number }>, ApiEnvelope<{ records: PlanningContainer[]; total: number }>>(containerBase, { params: query, signal }))
|
|
return { ...result, total: Number(result.total), records: result.records.map(row => ({
|
|
...row, orderCount: Number(row.orderCount), customerCount: Number(row.customerCount),
|
|
packages: row.packages == null ? null : Number(row.packages), unknownPackageOrders: Number(row.unknownPackageOrders),
|
|
warehouses: row.warehouses.map(warehouse => ({ ...warehouse, orderCount: Number(warehouse.orderCount), customerCount: Number(warehouse.customerCount) })),
|
|
})) }
|
|
}
|
|
|
|
export async function resolvePlanningContainer(containerId: string, warehouseId?: string) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<ContainerFollowupContext>, ApiEnvelope<ContainerFollowupContext>>(`${containerBase}/${encodeURIComponent(containerId)}`, { params: { warehouseId } }))
|
|
}
|
|
export async function getContainerFollowups(context: ContainerFollowupContext, page: number, size: number) {
|
|
const result = unwrapApiResponse(await http.get<ApiEnvelope<FollowupHistory>, ApiEnvelope<FollowupHistory>>(`${containerBase}/${encodeURIComponent(context.containerBookingId)}/follow-ups`, { params: { warehouseId: context.warehouseId || undefined, page, size } }))
|
|
return { ...result, history: { ...result.history, total: Number(result.history.total) }, versions: Object.fromEntries(Object.entries(result.versions).map(([stage, version]) => [stage, Number(version)])) as Record<FollowupStage, number> }
|
|
}
|
|
export async function saveContainerFollowup(context: ContainerFollowupContext, body: SaveFollowup) {
|
|
return unwrapApiResponse(await http.post<ApiEnvelope<FollowupRecord>, ApiEnvelope<FollowupRecord>>(`${containerBase}/${encodeURIComponent(context.containerBookingId)}/follow-ups`, body, { params: { warehouseId: context.warehouseId || undefined } }))
|
|
}
|