403 lines
12 KiB
TypeScript
403 lines
12 KiB
TypeScript
import { overseasHttp } from '@/shared/api/overseasHttp'
|
|
import { ApiResponseError, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
|
import type { InboundPlanDetailDto } from './inboundPlans'
|
|
|
|
export interface OverseasPageResult<T> {
|
|
records: T[]
|
|
page: number
|
|
size: number
|
|
total: number
|
|
pages: number
|
|
}
|
|
|
|
export type OverseasPagePayload<T> = Omit<OverseasPageResult<T>, 'page' | 'size' | 'total' | 'pages'> & {
|
|
page: number | string
|
|
size: number | string
|
|
total: number | string
|
|
pages: number | string
|
|
}
|
|
|
|
export function normalizeOverseasPage<T>(value: OverseasPagePayload<T>): OverseasPageResult<T> {
|
|
return { ...value, page: Number(value.page), size: Number(value.size), total: Number(value.total), pages: Number(value.pages) }
|
|
}
|
|
|
|
export interface LastMileOrderQuery {
|
|
page: number
|
|
size: number
|
|
orderNo?: string
|
|
orderType?: string
|
|
inboundCode?: string
|
|
lxCode?: string
|
|
containerNo?: string
|
|
currentStatus?: string
|
|
status?: string
|
|
warehouse?: string
|
|
sourceOrderNo?: string
|
|
transferWarehouseCode?: string
|
|
abnormal?: boolean
|
|
detained?: boolean
|
|
createdFrom?: string
|
|
createdTo?: string
|
|
}
|
|
|
|
export interface LastMileOrderSummaryDto {
|
|
id: string
|
|
customerId: string
|
|
sourceContainerBookingId?: string | null
|
|
orderNo: string
|
|
orderType?: string | null
|
|
orderTypeName?: string | null
|
|
inboundCode?: string | null
|
|
lxCode?: string | null
|
|
sourceOrderNo?: string | null
|
|
sourceInboundId?: string | null
|
|
customerName?: string | null
|
|
overseasWarehouseId?: string | null
|
|
overseasWarehouseCode?: string | null
|
|
overseasWarehouseName?: string | null
|
|
transferWarehouseName?: string | null
|
|
transferWarehouseId?: string | null
|
|
transferWarehouseCode?: string | null
|
|
fbaWarehouseCode?: string | null
|
|
containerNo?: string | null
|
|
containerStatus?: string | null
|
|
abnormal?: boolean | null
|
|
detained?: boolean | null
|
|
holdChargeable?: boolean | null
|
|
holdReason?: string | null
|
|
manualOverrideEnabled?: boolean | null
|
|
nodeSource?: 'CONTAINER' | 'MANUAL' | null
|
|
version?: number | null
|
|
palletCount?: number | null
|
|
packageCount?: number | null
|
|
volumeCbm?: number | null
|
|
productQuantity?: number | null
|
|
volumeWeightKg?: number | null
|
|
estimatedArrivalAt?: string | null
|
|
actualArrivalAt?: string | null
|
|
estimatedWarehouseAt?: string | null
|
|
inboundOrderNo?: string | null
|
|
/** Legacy read-only association retained for compatibility; absent from current service DTO. */
|
|
deliveryOrderNo?: string | null
|
|
outboundOrderNo?: string | null
|
|
relabelOrderNo?: string | null
|
|
relabelStatus?: string | null
|
|
palletMergeFlag?: string | null
|
|
/** Optional grouping facts returned by newer last-leg services. */
|
|
palletMergeBatchId?: string | null
|
|
palletMergeInboundNos?: string[] | null
|
|
currentStatus?: string | null
|
|
privateRemark?: string | null
|
|
customerRemark?: string | null
|
|
warehouseRemark?: string | null
|
|
createdAt?: string | null
|
|
}
|
|
|
|
export interface LastMileOrderDto extends LastMileOrderSummaryDto {
|
|
customerId: string
|
|
sourceOrderNo?: string | null
|
|
subOrderStatus?: string | null
|
|
updatedAt?: string | null
|
|
}
|
|
|
|
export interface LastMileCargoDto {
|
|
id: string
|
|
skuCode?: string | null
|
|
productNameZh?: string | null
|
|
productNameEn?: string | null
|
|
packageCount?: number | null
|
|
productQuantity?: number | null
|
|
palletCount?: number | null
|
|
grossWeightKg?: number | null
|
|
dimensionalWeightKg?: number | null
|
|
volumeCbm?: number | null
|
|
destinationWarehouseName?: string | null
|
|
fbaTrackingNo?: string | null
|
|
shipmentNo?: string | null
|
|
}
|
|
|
|
export interface LastMileStatusHistoryDto {
|
|
id: string
|
|
fromStatus?: string | null
|
|
toStatus?: string | null
|
|
remark?: string | null
|
|
createdAt?: string | null
|
|
}
|
|
|
|
export interface LastMileFeeDto {
|
|
id: string
|
|
customerId: string
|
|
orderId: string
|
|
feeItem: string
|
|
currency: string
|
|
amount: number
|
|
status: string
|
|
supplierDescription?: string | null
|
|
createdBy: string
|
|
createdAt: string
|
|
updatedAt: string
|
|
}
|
|
|
|
export interface LastMileOperationDto {
|
|
id: string
|
|
operationNode: string
|
|
operatorName: string
|
|
result: string
|
|
createdAt: string
|
|
}
|
|
|
|
export interface CreateLastMileFeePayload {
|
|
feeItem: string
|
|
currency: string
|
|
amount: number
|
|
status: string
|
|
supplierDescription?: string
|
|
}
|
|
|
|
export interface TransferDestinationPayload {
|
|
orderIds: string[]
|
|
warehouseId: string
|
|
warehouseCode: string
|
|
warehouseName: string
|
|
remark?: string
|
|
}
|
|
|
|
export interface ContainerPlanProgressQuery {
|
|
page: number
|
|
size: number
|
|
containerNo?: string
|
|
createdFrom?: string
|
|
createdTo?: string
|
|
}
|
|
|
|
export interface ContainerPlanProgressSummaryDto {
|
|
sourceContainerBookingId: string
|
|
containerNo: string | null
|
|
overseasWarehouseId: string | null
|
|
overseasWarehouseCode: string | null
|
|
overseasWarehouseName: string | null
|
|
assignmentVersion: number
|
|
canChangeReceivingWarehouse: boolean
|
|
receivingWarehouseLockReason: string | null
|
|
totalOrderCount: number
|
|
inboundCodeCount: number
|
|
missingInboundCodeCount: number
|
|
planCount: number
|
|
plannedInboundCodeCount: number
|
|
pendingPlanCount: number
|
|
plannedArrivalAt: string | null
|
|
allPlansReady: boolean
|
|
blockingReason: string | null
|
|
}
|
|
|
|
export interface ContainerPlanProgressItemDto {
|
|
orderId: string
|
|
customerId: string
|
|
orderNo: string | null
|
|
inboundCode: string | null
|
|
orderStatus: string
|
|
planId: string | null
|
|
planNo: string | null
|
|
planStatus: string | null
|
|
planValid: boolean
|
|
plannedArrivalAt: string | null
|
|
}
|
|
|
|
export interface ContainerPlanProgressDetailDto {
|
|
summary: ContainerPlanProgressSummaryDto
|
|
orders: ContainerPlanProgressItemDto[]
|
|
}
|
|
|
|
export interface ConvertContainerInboundPlanPayload {
|
|
sourceContainerBookingId: string
|
|
}
|
|
|
|
export type ManualNodeCode =
|
|
| 'DOMESTIC_CUSTOMS_COMPLETED'
|
|
| 'ESTIMATED_DEPARTURE'
|
|
| 'ACTUAL_DEPARTURE'
|
|
| 'ESTIMATED_ARRIVAL'
|
|
| 'OVERSEAS_CUSTOMS_COMPLETED'
|
|
| 'ACTUAL_ARRIVAL_WAITING_UNLOAD'
|
|
| 'PICKUP_APPOINTED'
|
|
| 'UNLOADED'
|
|
|
|
export interface ManualInboundPlanPayload {
|
|
customerId: string
|
|
orderId: string
|
|
expectedOrderVersion: number
|
|
plannedArrivalDate: string
|
|
palletCount: number
|
|
palletType: 'STANDARD' | 'WOOD' | 'PAPER'
|
|
assigned: boolean
|
|
mergeRequired: boolean
|
|
urgent: boolean
|
|
remark?: string
|
|
}
|
|
|
|
export interface HoldLastMileOrderPayload {
|
|
held: boolean
|
|
chargeable: boolean
|
|
reason: string
|
|
expectedVersion: number
|
|
}
|
|
|
|
export interface HoldLastMileOrderResponse {
|
|
orderId: string
|
|
held: boolean
|
|
chargeable: boolean
|
|
reason?: string | null
|
|
version: number
|
|
}
|
|
|
|
export interface ManualNodeValue {
|
|
nodeCode: ManualNodeCode
|
|
nodeAt: string | null
|
|
}
|
|
|
|
export interface ManualNodesResponse {
|
|
orderId: string
|
|
manualOverrideEnabled: boolean
|
|
nodes: Array<ManualNodeValue & { source: 'MANUAL' }>
|
|
version: number
|
|
}
|
|
|
|
export interface ChangeContainerReceivingWarehousePayload {
|
|
overseasWarehouseId: string
|
|
expectedVersion: number
|
|
remark?: string
|
|
}
|
|
|
|
export interface LastMileOrderDetailDto {
|
|
order: LastMileOrderDto
|
|
manualNodes?: ManualNodesResponse['nodes']
|
|
cargo: LastMileCargoDto[]
|
|
statusHistory: LastMileStatusHistoryDto[]
|
|
fees: LastMileFeeDto[]
|
|
operationHistory: LastMileOperationDto[]
|
|
}
|
|
|
|
export async function listLastMileOrders(query: LastMileOrderQuery) {
|
|
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<LastMileOrderSummaryDto>>, ApiEnvelope<OverseasPagePayload<LastMileOrderSummaryDto>>>(
|
|
'/platform/v1/last-leg/orders',
|
|
{ params: query },
|
|
)
|
|
return normalizeOverseasPage(unwrapApiResponse(response))
|
|
}
|
|
|
|
export async function getLastMileOrder(orderId: string) {
|
|
const response = await overseasHttp.get<ApiEnvelope<LastMileOrderDetailDto>, ApiEnvelope<LastMileOrderDetailDto>>(
|
|
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}`,
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function downloadLastMileOrders(query: Partial<LastMileOrderQuery>) {
|
|
const { page: _page, size: _size, ...filters } = query
|
|
const params = Object.fromEntries(
|
|
Object.entries(filters).filter(([, value]) => value !== undefined && value !== null && value !== ''),
|
|
)
|
|
const blob = await overseasHttp.get<Blob, Blob>('/platform/v1/last-leg/orders/export', { params, responseType: 'blob' })
|
|
if (!blob.type.toLowerCase().includes('json')) return blob
|
|
let error: Partial<ApiEnvelope<unknown>>
|
|
try {
|
|
error = JSON.parse(await blob.text()) as Partial<ApiEnvelope<unknown>>
|
|
} catch {
|
|
throw new ApiResponseError('导出接口返回了非 CSV 内容', 'INVALID_EXPORT_RESPONSE')
|
|
}
|
|
throw new ApiResponseError(
|
|
error.message || '尾程订单导出失败',
|
|
error.code || 'INVALID_EXPORT_RESPONSE',
|
|
undefined,
|
|
error.data,
|
|
error.requestId,
|
|
error.traceId,
|
|
)
|
|
}
|
|
|
|
export async function createLastMileOrderFee(orderId: string, payload: CreateLastMileFeePayload) {
|
|
const response = await overseasHttp.post<ApiEnvelope<LastMileFeeDto>, ApiEnvelope<LastMileFeeDto>>(
|
|
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}/fees`, payload,
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function transferLastMileOrders(payload: TransferDestinationPayload) {
|
|
const response = await overseasHttp.put<ApiEnvelope<number>, ApiEnvelope<number>>(
|
|
'/platform/v1/last-leg/orders/destination', payload,
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function listContainerPlanProgress(query: ContainerPlanProgressQuery): Promise<OverseasPageResult<ContainerPlanProgressSummaryDto>> {
|
|
const response = await overseasHttp.get<ApiEnvelope<OverseasPagePayload<ContainerPlanProgressSummaryDto>>, ApiEnvelope<OverseasPagePayload<ContainerPlanProgressSummaryDto>>>(
|
|
'/platform/v1/last-leg/orders/container-summaries',
|
|
{ params: query },
|
|
)
|
|
return normalizeOverseasPage(unwrapApiResponse(response))
|
|
}
|
|
|
|
export async function getContainerPlanProgress(sourceContainerBookingId: string) {
|
|
const response = await overseasHttp.get<ApiEnvelope<ContainerPlanProgressDetailDto>, ApiEnvelope<ContainerPlanProgressDetailDto>>(
|
|
`/platform/v1/last-leg/orders/container-summaries/${encodeURIComponent(sourceContainerBookingId)}`,
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function createManualInboundPlan(payload: ManualInboundPlanPayload, idempotencyKey: string) {
|
|
const response = await overseasHttp.post<ApiEnvelope<InboundPlanDetailDto>, ApiEnvelope<InboundPlanDetailDto>>(
|
|
'/platform/v1/last-leg/inbound-plans/manual', payload,
|
|
{ headers: { 'Idempotency-Key': idempotencyKey } },
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function updateLastMileOrderHold(orderId: string, payload: HoldLastMileOrderPayload, idempotencyKey: string) {
|
|
const response = await overseasHttp.put<ApiEnvelope<HoldLastMileOrderResponse>, ApiEnvelope<HoldLastMileOrderResponse>>(
|
|
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}/hold`, payload,
|
|
{ headers: { 'Idempotency-Key': idempotencyKey } },
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function getLastMileOrderManualNodes(orderId: string) {
|
|
const response = await overseasHttp.get<ApiEnvelope<ManualNodesResponse>, ApiEnvelope<ManualNodesResponse>>(
|
|
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}/manual-nodes`,
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function updateLastMileOrderManualNodes(
|
|
orderId: string,
|
|
expectedVersion: number,
|
|
nodes: ManualNodeValue[],
|
|
idempotencyKey: string,
|
|
) {
|
|
const response = await overseasHttp.put<ApiEnvelope<ManualNodesResponse>, ApiEnvelope<ManualNodesResponse>>(
|
|
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}/manual-nodes`,
|
|
{ expectedVersion, nodes },
|
|
{ headers: { 'Idempotency-Key': idempotencyKey } },
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function changeContainerReceivingWarehouse(
|
|
sourceContainerBookingId: string,
|
|
payload: ChangeContainerReceivingWarehousePayload,
|
|
idempotencyKey: string,
|
|
) {
|
|
const response = await overseasHttp.put<ApiEnvelope<ContainerPlanProgressSummaryDto>, ApiEnvelope<ContainerPlanProgressSummaryDto>>(
|
|
`/platform/v1/last-leg/orders/container-summaries/${encodeURIComponent(sourceContainerBookingId)}/receiving-warehouse`,
|
|
payload,
|
|
{ headers: { 'Idempotency-Key': idempotencyKey } },
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|
|
|
|
export async function convertLastMileContainerToInboundPlans(payload: ConvertContainerInboundPlanPayload) {
|
|
const response = await overseasHttp.post<ApiEnvelope<InboundPlanDetailDto[]>, ApiEnvelope<InboundPlanDetailDto[]>>(
|
|
'/platform/v1/last-leg/inbound-plans/convert-container', payload,
|
|
)
|
|
return unwrapApiResponse(response)
|
|
}
|