93 lines
7.5 KiB
TypeScript
93 lines
7.5 KiB
TypeScript
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
|
|
|
export const workTypes = ['PICKUP', 'WAREHOUSE_RECEIPT', 'DELIVERY', 'TRANSPORT', 'EMPTY_RETURN'] as const
|
|
export type WorkType = typeof workTypes[number] | 'ROUTE'
|
|
export type WorkFlow = 'TRANSFER' | 'DIRECT' | 'UNKNOWN'
|
|
export type WorkView = 'ALL' | 'FOLLOW_TODAY' | 'OVERDUE' | 'UNDATED' | 'REVIEW'
|
|
export type WorkBucket = 'PENDING' | 'CONFIRMED' | 'NO_BOOKING_REQUIRED' | 'REVIEW' | 'COMPLETED'
|
|
export interface WorkSlot { date: string; startTime: string | null; endDate: string | null; endTime: string | null; timezone: string | null }
|
|
export const workCommands = ['CONFIRM_ROUTE', 'SAVE_FOLLOWUP', 'RESOLVE_REVIEW', 'SAVE_REQUIREMENT', 'HANDOFF_RETURN', 'ACCEPT_RETURN'] as const
|
|
export type WorkCommandName = typeof workCommands[number]
|
|
export const actionCodes = ['VIEW_DETAIL', 'SAVE_PLAN', 'REQUEST_BOOKING', 'RECORD_CONFIRMATION', 'CHANGE_BOOKING', 'CANCEL_BOOKING', 'VERIFY_CARGO', 'EDIT_BATCH', 'ARRANGE_TRANSPORT', 'RECORD_EXECUTION', 'CONFIRM_ROUTE', 'SAVE_FOLLOWUP', 'RESOLVE_REVIEW', 'SAVE_REQUIREMENT', 'HANDOFF_RETURN', 'ACCEPT_RETURN', 'VIEW_HISTORY'] as const
|
|
export interface SourceReference { customerId: string; sourceKey: string; destinationKey: string | null }
|
|
export interface BusinessReference { kind: string; key: string; businessNo: string | null; label: string }
|
|
export type ActionTarget =
|
|
| { kind: 'WORK_COMMAND'; command: WorkCommandName }
|
|
| { kind: 'CARGO_VERIFICATION'; source: SourceReference }
|
|
| { kind: 'FBA_APPOINTMENT'; appointmentId: string | null; batchKey: string | null; sources: SourceReference[] }
|
|
| { kind: 'FIRSTLEG_CONTAINER'; containerBookingId: string }
|
|
| { kind: 'FLEET_TRIP'; tripId: string | null; batchKeys: string[] }
|
|
| { kind: 'WAREHOUSE_RECEIPT'; inboundPlanId: string | null; inboundPlanNo: string | null; customerId: string | null; containerBookingId: string }
|
|
| { kind: 'READ_ONLY'; references: BusinessReference[] }
|
|
export interface WorkAction { code: typeof actionCodes[number]; label: string; targetWorkItemKey: string | null; target: ActionTarget }
|
|
export interface Responsibility { kind: 'USER' | 'SUPPLIER' | 'EXTERNAL'; id: string | null; name: string }
|
|
export interface WorkItem {
|
|
workItemKey: string; version: number; type: WorkType; flow: WorkFlow
|
|
title: string; businessNo: string | null; containers: Array<{ containerBookingId: string; bookingNo: string | null; containerNo: string | null }>
|
|
requirementKey: string | null; batchKey: string | null; batchNo: string | null
|
|
originName: string | null; destinationName: string | null
|
|
planStatus: 'UNPLANNED' | 'TARGET_SET' | 'AWAITING_REPLY' | 'CONFIRMED' | 'NOT_REQUIRED'
|
|
executionStatus: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'; reviewRequired: boolean
|
|
target: WorkSlot | null; confirmed: WorkSlot | null
|
|
actual: { occurredAt: string | null; localOccurredAt: string | null; timezone: string | null } | null
|
|
calendarDate: string | null; dateBasis: 'ACTUAL' | 'CONFIRMED' | 'TARGET' | 'NONE'; timezone: string | null
|
|
owner: { id: string; name: string } | null; followupDate: string | null; overdueReasons: string[]
|
|
allowedActions: WorkAction[]; nextAction: WorkAction | null; blockReasons: string[]; missingFields: string[]
|
|
}
|
|
export interface WorkDetail {
|
|
item: WorkItem; relatedItems: WorkItem[]
|
|
references: BusinessReference[]
|
|
history: Array<{ occurredAt: string; operatorName: string | null; action: string; summary: string }>
|
|
review: { version: number; reasons: string[] } | null
|
|
requirement: { bookingRequired: boolean; receivingRequirements: string; reason: string | null } | null
|
|
returnHandoff: { version: number; from: Responsibility; to: Responsibility; status: 'PENDING_ACCEPTANCE' | 'ACCEPTED'; reason: string } | null
|
|
}
|
|
export interface WorkSummary { all: number; followToday: number; overdue: number; undated: number; review: number }
|
|
export interface WorkCounts { pending: number; confirmed: number; noBookingRequired: number; review: number; completed: number }
|
|
export interface WorkDay { date: string; total: number; typeCounts: Partial<Record<WorkType, number>>; counts: WorkCounts }
|
|
export interface WorkCalendar { from: string; to: string; total: number; counts: WorkCounts; days: WorkDay[] }
|
|
export interface WorkPage { records: WorkItem[]; page: number; size: number; total: number; pages: number }
|
|
export interface WorkCalendarQuery { from: string; to: string; flow?: WorkFlow; types?: WorkType[] }
|
|
export interface WorkItemsQuery { view: WorkView; page: number; size: number; date?: string; flow?: WorkFlow; types?: WorkType[]; containerBookingId?: string; bucket?: WorkBucket; keyword?: string }
|
|
export type WorkCommandPayload =
|
|
| { command: 'CONFIRM_ROUTE'; payload: { flow: 'TRANSFER' | 'DIRECT'; reason: string | null } }
|
|
| { command: 'SAVE_FOLLOWUP'; payload: { ownerId: string; followupDate: string | null; note: string } }
|
|
| { command: 'RESOLVE_REVIEW'; payload: { reviewVersion: number; note: string } }
|
|
| { command: 'SAVE_REQUIREMENT'; payload: { target: WorkSlot | null; bookingRequired: boolean; receivingRequirements: string; reason: string } }
|
|
| { command: 'HANDOFF_RETURN'; payload: { recipient: Responsibility; reason: string } }
|
|
| { command: 'ACCEPT_RETURN'; payload: { handoffVersion: number; note: string | null } }
|
|
export type WorkCommand = WorkCommandPayload & { expectedVersion: number; idempotencyKey: string }
|
|
export interface WorkCommandResult { item: WorkItem; affectedWorkItemKeys: string[] }
|
|
export interface BatchWorkResolution { batchKey: string; customerId: string | null; detail: WorkDetail | null; candidates: Array<{ customerId: string; workItemKey: string; batchNo: string | null }> }
|
|
const base = '/platform/v1/last-leg/appointment-work'
|
|
|
|
// Repeated types parameters are part of v1; Axios' default types[] syntax is not.
|
|
function params(query: WorkCalendarQuery | WorkItemsQuery) {
|
|
const result = new URLSearchParams()
|
|
for (const [key, value] of Object.entries(query)) {
|
|
if (value === undefined) continue
|
|
if (Array.isArray(value)) value.forEach(type => result.append(key, type))
|
|
else result.set(key, String(value))
|
|
}
|
|
return result
|
|
}
|
|
export async function getWorkCalendar(query: WorkCalendarQuery, signal?: AbortSignal) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<WorkCalendar>, ApiEnvelope<WorkCalendar>>(`${base}/calendar`, { params: params(query), signal }))
|
|
}
|
|
export async function getWorkSummary(signal?: AbortSignal) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<WorkSummary>, ApiEnvelope<WorkSummary>>(`${base}/summary`, { signal }))
|
|
}
|
|
export async function getWorkItems(query: WorkItemsQuery, signal?: AbortSignal) {
|
|
const result = unwrapApiResponse(await http.get<ApiEnvelope<WorkPage>, ApiEnvelope<WorkPage>>(`${base}/items`, { params: params(query), signal }))
|
|
return { ...result, page: Number(result.page), size: Number(result.size), total: Number(result.total), pages: Number(result.pages) }
|
|
}
|
|
export async function getWorkDetail(key: string, signal?: AbortSignal) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<WorkDetail>, ApiEnvelope<WorkDetail>>(`${base}/items/${encodeURIComponent(key)}`, { signal }))
|
|
}
|
|
export async function resolveBatchWork(batchKey: string, customerId?: string, signal?: AbortSignal) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<BatchWorkResolution>, ApiEnvelope<BatchWorkResolution>>(`${base}/items/resolve`, { params: { batchKey, customerId }, signal }))
|
|
}
|
|
export async function submitWorkCommand(key: string, command: WorkCommand) {
|
|
return unwrapApiResponse(await http.post<ApiEnvelope<WorkCommandResult>, ApiEnvelope<WorkCommandResult>>(`${base}/items/${encodeURIComponent(key)}/commands`, command))
|
|
}
|