2026-08-10 15:36:53 +08:00
|
|
|
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
|
|
|
|
|
|
|
|
|
export type LoadingListMode = 'preload' | 'confirmed'
|
|
|
|
|
|
|
|
|
|
export interface LoadingCustomer {
|
|
|
|
|
accountId: string
|
|
|
|
|
customerName: string | null
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 17:04:00 +08:00
|
|
|
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[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 15:36:53 +08:00
|
|
|
export interface PreloadSummary {
|
|
|
|
|
planId: string
|
2026-08-12 17:29:35 +08:00
|
|
|
containerBookingId: string
|
2026-08-10 15:36:53 +08:00
|
|
|
preloadNo: string
|
|
|
|
|
containerNo: string | null
|
2026-08-15 17:04:00 +08:00
|
|
|
cabinetNo: string | null
|
2026-08-10 15:36:53 +08:00
|
|
|
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
|
2026-08-15 17:04:00 +08:00
|
|
|
readiness: PreloadReadiness
|
2026-08-10 15:36:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ConfirmedSummary {
|
|
|
|
|
planId: string
|
2026-08-12 17:29:35 +08:00
|
|
|
containerBookingId: string
|
2026-08-10 15:36:53 +08:00
|
|
|
loadingListNo: string
|
|
|
|
|
warehouseId: string | null
|
|
|
|
|
warehouseName: string | null
|
|
|
|
|
containerNo: string | null
|
2026-08-15 17:04:00 +08:00
|
|
|
cabinetNo: string | null
|
2026-08-10 15:36:53 +08:00
|
|
|
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
|
2026-08-18 16:09:43 +08:00
|
|
|
itemVersion: number
|
2026-08-10 15:36:53 +08:00
|
|
|
accountId: string
|
|
|
|
|
customerName: string | null
|
2026-09-08 17:38:09 +08:00
|
|
|
customsTaxMode: 'TAX_REFUND' | 'BUY_DECLARATION' | null
|
|
|
|
|
taxRefundFlag: boolean | null
|
2026-08-17 19:57:32 +08:00
|
|
|
orderId: string
|
|
|
|
|
orderNo: string
|
2026-08-10 15:36:53 +08:00
|
|
|
inboundId: string
|
|
|
|
|
inboundDetailId: string
|
|
|
|
|
inboundCode: string | null
|
2026-08-17 19:57:32 +08:00
|
|
|
containerNo: string | null
|
2026-08-10 15:36:53 +08:00
|
|
|
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
|
2026-09-01 11:10:22 +08:00
|
|
|
operationId: string
|
2026-08-10 15:36:53 +08:00
|
|
|
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
|
2026-08-14 12:50:42 +08:00
|
|
|
customerId?: string
|
2026-08-10 17:42:17 +08:00
|
|
|
warehouseId?: string
|
2026-08-10 15:36:53 +08:00
|
|
|
keyword?: string
|
|
|
|
|
createdFrom?: string
|
|
|
|
|
createdTo?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TransportUpdateRequest {
|
|
|
|
|
containerNo: string | null
|
|
|
|
|
voyageNo: string | null
|
|
|
|
|
expectedVersion: number
|
|
|
|
|
operationReason: string | null
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 16:09:43 +08:00
|
|
|
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 }>
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 17:04:00 +08:00
|
|
|
export interface PreloadConfirmationResponse {
|
|
|
|
|
planId: string
|
|
|
|
|
containerBookingId: string
|
|
|
|
|
confirmationStatus: 'CONFIRMED'
|
|
|
|
|
version: number
|
|
|
|
|
readiness: PreloadReadiness
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 15:36:53 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 17:04:00 +08:00
|
|
|
function normalizePreloadReadiness(raw: PreloadReadiness): PreloadReadiness {
|
|
|
|
|
return {
|
|
|
|
|
...raw,
|
|
|
|
|
oceanBookingId: optionalId(raw.oceanBookingId),
|
|
|
|
|
pdaTaskId: optionalId(raw.pdaTaskId),
|
|
|
|
|
blockingReasons: raw.blockingReasons || [],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 15:36:53 +08:00
|
|
|
function normalizePreloadSummary(raw: PreloadSummary): PreloadSummary {
|
|
|
|
|
return {
|
|
|
|
|
...raw,
|
|
|
|
|
planId: requiredId(raw.planId),
|
2026-08-12 17:29:35 +08:00
|
|
|
containerBookingId: requiredId(raw.containerBookingId),
|
2026-08-10 15:36:53 +08:00
|
|
|
warehouseId: optionalId(raw.warehouseId),
|
|
|
|
|
skuCount: String(raw.skuCount ?? '0'),
|
|
|
|
|
totalPackageCount: String(raw.totalPackageCount ?? '0'),
|
2026-08-15 17:04:00 +08:00
|
|
|
readiness: normalizePreloadReadiness(raw.readiness),
|
2026-08-10 15:36:53 +08:00
|
|
|
customers: (raw.customers || []).map((customer) => ({
|
|
|
|
|
...customer,
|
|
|
|
|
accountId: requiredId(customer.accountId),
|
|
|
|
|
})),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeConfirmedSummary(raw: ConfirmedSummary): ConfirmedSummary {
|
|
|
|
|
return {
|
|
|
|
|
...raw,
|
|
|
|
|
planId: requiredId(raw.planId),
|
2026-08-12 17:29:35 +08:00
|
|
|
containerBookingId: requiredId(raw.containerBookingId),
|
2026-08-10 15:36:53 +08:00
|
|
|
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),
|
2026-08-18 16:09:43 +08:00
|
|
|
itemVersion: Number(raw.itemVersion),
|
2026-08-10 15:36:53 +08:00
|
|
|
accountId: requiredId(raw.accountId),
|
2026-08-17 19:57:32 +08:00
|
|
|
orderId: requiredId(raw.orderId),
|
2026-08-10 15:36:53 +08:00
|
|
|
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),
|
2026-09-01 11:10:22 +08:00
|
|
|
operationId: requiredId(raw.operationId),
|
2026-08-10 15:36:53 +08:00
|
|
|
operatorId: requiredId(raw.operatorId),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-15 17:04:00 +08:00
|
|
|
|
2026-09-09 17:27:00 +08:00
|
|
|
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[]
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 15:36:53 +08:00
|
|
|
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`,
|
2026-08-11 14:23:46 +08:00
|
|
|
{ params: query },
|
2026-08-10 15:36:53 +08:00
|
|
|
)
|
|
|
|
|
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),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 16:09:43 +08:00
|
|
|
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) })),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 17:04:00 +08:00
|
|
|
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),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 15:36:53 +08:00
|
|
|
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),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-09 17:27:00 +08:00
|
|
|
|
|
|
|
|
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 },
|
|
|
|
|
)
|
|
|
|
|
}
|