Files
admin-web/src/modules/loading-lists/api/platformLoadingLists.ts
T

407 lines
12 KiB
TypeScript

import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
export type LoadingListMode = 'preload' | 'confirmed'
export interface LoadingCustomer {
accountId: string
customerName: string | null
}
export type PreloadConfirmationStatus = 'PENDING' | 'PARTIALLY_CONFIRMED' | 'CONFIRMED'
export type PreloadBlockingReason =
| 'LOADING_PLAN_NOT_EDITING'
| 'PRELOAD_LINK_MISSING'
| 'OCEAN_BOOKING_NOT_LINKED'
| 'OCEAN_BOOKING_NOT_RELEASED'
| 'PDA_TASK_ALREADY_CREATED'
| 'PRELOAD_ALREADY_CONFIRMED'
export interface PreloadReadiness {
containerNoReady: boolean
preloadLinkCount: number
confirmedPreloadCount: number
oceanBookingId: string | null
oceanBookingStatus: 'CONFIRMED' | 'CHANGE_PENDING' | 'RELEASED' | 'CANCELLED' | null
pdaTaskId: string | null
pdaTaskStatus: string | null
confirmationStatus: PreloadConfirmationStatus
canConfirm: boolean
readyForPda: boolean
blockingReasons: PreloadBlockingReason[]
}
export interface PreloadSummary {
planId: string
containerBookingId: string
preloadNo: string
containerNo: string | null
cabinetNo: string | null
customers: LoadingCustomer[]
warehouseId: string | null
warehouseName: string | null
vesselName: string | null
voyageNo: string | null
containerType: string | null
status: string
skuCount: string
totalPackageCount: string
totalVolumeCbm: number
totalGrossWeightKg: number
createdAt: string
version: number
readiness: PreloadReadiness
}
export interface ConfirmedSummary {
planId: string
containerBookingId: string
loadingListNo: string
warehouseId: string | null
warehouseName: string | null
containerNo: string | null
cabinetNo: string | null
status: string
totalPackageCount: string
totalGrossWeightKg: number
totalNetWeightKg: number
totalVolumeCbm: number
totalQuantity: string
remark: string | null
createdBy: string | null
createdByName: string | null
createdAt: string
version: number
}
export type LoadingListSummary = PreloadSummary | ConfirmedSummary
export interface LoadingItem {
itemId: string
itemVersion: number
accountId: string
customerName: string | null
customsTaxMode: 'TAX_REFUND' | 'BUY_DECLARATION' | null
taxRefundFlag: boolean | null
orderId: string
orderNo: string
inboundId: string
inboundDetailId: string
inboundCode: string | null
containerNo: string | null
skuCode: string | null
destinationWarehouse: string | null
chineseName: string | null
englishName: string | null
goodsImageUrl: string | null
packageCount: number
palletCount: number
grossWeightKg: number
netWeightKg: number
volumeCbm: number
dimensionalWeightKg: number
productQuantity: string
unitsPerPackage: number
fbaTrackingNo: string | null
shipmentNo: string | null
shippingMark: string | null
internalRemark: string | null
customerRemark: string | null
warehouseRemark: string | null
loadSequence: number
frozenAt: string | null
}
export interface LoadingHistory {
historyId: string
itemId: string | null
operationId: string
actionType: string
fromStatus: string | null
toStatus: string | null
beforeSnapshot: string | null
afterSnapshot: string | null
operatorId: string
operatorName: string | null
operationReason: string | null
occurredAt: string
}
export interface LoadingDetail<T extends LoadingListSummary = LoadingListSummary> {
summary: T
items: LoadingItem[]
histories: LoadingHistory[]
}
export interface LoadingListPage<T extends LoadingListSummary = LoadingListSummary> {
records: T[]
page: number
size: number
total: number
pages: number
}
export interface LoadingListQuery {
page: number
size: number
customerId?: string
warehouseId?: string
keyword?: string
createdFrom?: string
createdTo?: string
}
export interface TransportUpdateRequest {
containerNo: string | null
voyageNo: string | null
expectedVersion: number
operationReason: string | null
}
export interface WarehouseRemarkUpdateRequest {
warehouseRemark: string | null
expectedPlanVersion: number
expectedItemVersion: number
operationId: string
}
export interface WarehouseRemarkUpdateResponse {
id: string
version: number
items: Array<{ id: string; version: number; warehouseRemark: string | null }>
}
export interface PreloadConfirmationResponse {
planId: string
containerBookingId: string
confirmationStatus: 'CONFIRMED'
version: number
readiness: PreloadReadiness
}
type RawId = string | number | null | undefined
type RawPage<T extends LoadingListSummary> = Omit<LoadingListPage<T>, 'page' | 'size' | 'total' | 'pages'> & {
page: string | number
size: string | number
total: string | number
pages: string | number
}
const basePath = '/platform/v1/first-leg'
function requiredId(value: RawId) {
return String(value ?? '')
}
function optionalId(value: RawId) {
return value === null || value === undefined ? null : String(value)
}
function normalizePreloadReadiness(raw: PreloadReadiness): PreloadReadiness {
return {
...raw,
oceanBookingId: optionalId(raw.oceanBookingId),
pdaTaskId: optionalId(raw.pdaTaskId),
blockingReasons: raw.blockingReasons || [],
}
}
function normalizePreloadSummary(raw: PreloadSummary): PreloadSummary {
return {
...raw,
planId: requiredId(raw.planId),
containerBookingId: requiredId(raw.containerBookingId),
warehouseId: optionalId(raw.warehouseId),
skuCount: String(raw.skuCount ?? '0'),
totalPackageCount: String(raw.totalPackageCount ?? '0'),
readiness: normalizePreloadReadiness(raw.readiness),
customers: (raw.customers || []).map((customer) => ({
...customer,
accountId: requiredId(customer.accountId),
})),
}
}
function normalizeConfirmedSummary(raw: ConfirmedSummary): ConfirmedSummary {
return {
...raw,
planId: requiredId(raw.planId),
containerBookingId: requiredId(raw.containerBookingId),
warehouseId: optionalId(raw.warehouseId),
createdBy: optionalId(raw.createdBy),
totalPackageCount: String(raw.totalPackageCount ?? '0'),
totalQuantity: String(raw.totalQuantity ?? '0'),
}
}
function normalizeItem(raw: LoadingItem): LoadingItem {
return {
...raw,
itemId: requiredId(raw.itemId),
itemVersion: Number(raw.itemVersion),
accountId: requiredId(raw.accountId),
orderId: requiredId(raw.orderId),
inboundId: requiredId(raw.inboundId),
inboundDetailId: requiredId(raw.inboundDetailId),
productQuantity: String(raw.productQuantity ?? '0'),
}
}
function normalizeHistory(raw: LoadingHistory): LoadingHistory {
return {
...raw,
historyId: requiredId(raw.historyId),
itemId: optionalId(raw.itemId),
operationId: requiredId(raw.operationId),
operatorId: requiredId(raw.operatorId),
}
}
export interface PalletLabel {
labelId: string
itemId: string
accountId: string
inboundId: string
inboundCode: string
containerNo: string
destinationWarehouseCode: string
destinationWarehouseName: string | null
palletIndex: number
palletCode: string
}
export interface PalletLabelGroup {
destinationWarehouseCode: string
destinationWarehouseName: string | null
totalCount: number
labels: PalletLabel[]
}
export interface PalletLabelManifest {
planId: string
planNo: string
containerNo: string
totalCount: number
groups: PalletLabelGroup[]
}
function normalizePage<T extends LoadingListSummary>(raw: RawPage<T>, normalize: (row: T) => T): LoadingListPage<T> {
return {
records: (raw.records || []).map(normalize),
page: Number(raw.page),
size: Number(raw.size),
total: Number(raw.total),
pages: Number(raw.pages),
}
}
function idempotencyKey(planId: string) {
return `admin-preload-transport-${planId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
}
export async function listPreloadPlans(query: LoadingListQuery) {
const response = await http.get<ApiEnvelope<RawPage<PreloadSummary>>, ApiEnvelope<RawPage<PreloadSummary>>>(
`${basePath}/loading-plans`,
{ params: query },
)
return normalizePage(unwrapApiResponse(response), normalizePreloadSummary)
}
export async function getPreloadPlan(planId: string, warehouseId: string) {
const response = await http.get<ApiEnvelope<LoadingDetail<PreloadSummary>>, ApiEnvelope<LoadingDetail<PreloadSummary>>>(
`${basePath}/loading-plans/${planId}`,
{ params: { warehouseId } },
)
const result = unwrapApiResponse(response)
return {
summary: normalizePreloadSummary(result.summary),
items: (result.items || []).map(normalizeItem),
histories: (result.histories || []).map(normalizeHistory),
}
}
export async function updatePreloadTransport(planId: string, warehouseId: string, request: TransportUpdateRequest) {
const response = await http.patch<ApiEnvelope<LoadingDetail<PreloadSummary>>, ApiEnvelope<LoadingDetail<PreloadSummary>>>(
`${basePath}/loading-plans/${planId}/transport`,
request,
{ params: { warehouseId }, headers: { 'Idempotency-Key': idempotencyKey(planId) } },
)
const result = unwrapApiResponse(response)
return {
summary: normalizePreloadSummary(result.summary),
items: (result.items || []).map(normalizeItem),
histories: (result.histories || []).map(normalizeHistory),
}
}
export async function updateLoadingItemWarehouseRemark(planId: string, itemId: string, request: WarehouseRemarkUpdateRequest) {
const response = await http.patch<ApiEnvelope<WarehouseRemarkUpdateResponse>, ApiEnvelope<WarehouseRemarkUpdateResponse>>(
`${basePath}/smart-loading/plans/${encodeURIComponent(planId)}/items/${encodeURIComponent(itemId)}/warehouse-remark`,
request,
)
const result = unwrapApiResponse(response)
return {
...result,
id: requiredId(result.id),
items: (result.items || []).map((item) => ({ ...item, id: requiredId(item.id), version: Number(item.version) })),
}
}
export async function confirmPreloadPlan(planId: string, expectedVersion: number, operationId: string) {
const response = await http.post<ApiEnvelope<PreloadConfirmationResponse>, ApiEnvelope<PreloadConfirmationResponse>>(
`${basePath}/loading-plans/${encodeURIComponent(planId)}/confirm`,
{ expectedVersion },
{ headers: { 'Idempotency-Key': operationId } },
)
const result = unwrapApiResponse(response)
return {
...result,
planId: requiredId(result.planId),
containerBookingId: requiredId(result.containerBookingId),
readiness: normalizePreloadReadiness(result.readiness),
}
}
export async function listConfirmedLoadingLists(query: LoadingListQuery) {
const response = await http.get<ApiEnvelope<RawPage<ConfirmedSummary>>, ApiEnvelope<RawPage<ConfirmedSummary>>>(
`${basePath}/loading-lists`,
{ params: query },
)
return normalizePage(unwrapApiResponse(response), normalizeConfirmedSummary)
}
export async function getConfirmedLoadingList(planId: string, warehouseId: string) {
const response = await http.get<ApiEnvelope<LoadingDetail<ConfirmedSummary>>, ApiEnvelope<LoadingDetail<ConfirmedSummary>>>(
`${basePath}/loading-lists/${planId}`,
{ params: { warehouseId } },
)
const result = unwrapApiResponse(response)
return {
summary: normalizeConfirmedSummary(result.summary),
items: (result.items || []).map(normalizeItem),
histories: (result.histories || []).map(normalizeHistory),
}
}
export async function preparePalletLabels(planId: string, warehouseId: string) {
const response = await http.post<ApiEnvelope<PalletLabelManifest>, ApiEnvelope<PalletLabelManifest>>(
`${basePath}/loading-plans/${encodeURIComponent(planId)}/pallet-labels/prepare`, null, { params: { warehouseId } },
)
const result = unwrapApiResponse(response)
return {
...result,
planId: requiredId(result.planId),
groups: (result.groups || []).map((group) => ({
...group,
labels: (group.labels || []).map((label) => ({ ...label, labelId: requiredId(label.labelId), itemId: requiredId(label.itemId), accountId: requiredId(label.accountId), inboundId: requiredId(label.inboundId) })),
})),
}
}
export function printPalletLabels(planId: string, warehouseId: string, destinationWarehouseCode?: string, count?: number) {
return http.post<Blob, Blob>(
`${basePath}/loading-plans/${encodeURIComponent(planId)}/pallet-labels/print`,
{ destinationWarehouseCode: destinationWarehouseCode || null, count: count || null },
{ params: { warehouseId }, responseType: 'blob', timeout: 60_000 },
)
}