2026-08-06 13:56:04 +08:00
|
|
|
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
|
|
|
|
|
|
|
|
|
const inboundBasePath = '/platform/v1/inbounds'
|
|
|
|
|
|
2026-08-10 12:05:20 +08:00
|
|
|
export interface OutboundGateItem { code: string; name: string; passed: boolean; actual?: string | null; requirement?: string | null }
|
|
|
|
|
export interface OutboundDispatchAggregate {
|
|
|
|
|
orderId: number | string | null
|
|
|
|
|
orderNo: string | null
|
|
|
|
|
inboundId: number | string | null
|
|
|
|
|
inboundCode: string | null
|
|
|
|
|
containerBookingId: number | string | null
|
|
|
|
|
containerNo: string | null
|
|
|
|
|
oceanBookingId: number | string | null
|
|
|
|
|
oceanBookingStatus: string | null
|
|
|
|
|
truckAppointmentId: number | string | null
|
|
|
|
|
truckAppointmentStatus: string | null
|
|
|
|
|
customsStatus?: string | null
|
|
|
|
|
sailingStatus?: string | null
|
|
|
|
|
gate: { passed: boolean; items: OutboundGateItem[] } | null
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 13:56:04 +08:00
|
|
|
export type PreloadStatus = 'PENDING' | 'CONFIRMING' | 'CONFIRMED' | string
|
2026-08-10 12:05:20 +08:00
|
|
|
/** 五类装柜取证。旧接口仍使用 PHOTO_STEP_* / SEAL 编码,页面统一映射为业务名称。 */
|
2026-08-06 13:56:04 +08:00
|
|
|
export type EvidenceType = 'PHOTO_STEP_1' | 'PHOTO_STEP_2' | 'PHOTO_STEP_3' | 'PHOTO_STEP_4' | 'SEAL'
|
|
|
|
|
|
|
|
|
|
export interface PreloadItem {
|
|
|
|
|
containerCode: string | null
|
|
|
|
|
cargoReference: string | null
|
|
|
|
|
packageCount: number | null
|
|
|
|
|
volumeCbm: number | null
|
|
|
|
|
remark: string | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PreloadPlan {
|
|
|
|
|
id: number | string
|
|
|
|
|
inboundId: number | string
|
|
|
|
|
tenantId: number | string
|
|
|
|
|
status: PreloadStatus
|
|
|
|
|
version: number
|
|
|
|
|
items: PreloadItem[]
|
|
|
|
|
remark: string | null
|
|
|
|
|
createdAt: string | null
|
|
|
|
|
updatedAt: string | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SplitActionResponse {
|
|
|
|
|
mainStatus: string
|
|
|
|
|
preloadPlan: PreloadPlan | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PickingTask {
|
|
|
|
|
id: number | string
|
|
|
|
|
inboundId: number | string
|
|
|
|
|
status: string
|
|
|
|
|
version: number
|
|
|
|
|
expectedCount: number | null
|
|
|
|
|
pickedCount: number | null
|
|
|
|
|
remark: string | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Evidence {
|
|
|
|
|
id: number | string
|
|
|
|
|
inboundId: number | string
|
|
|
|
|
tenantId: number | string
|
|
|
|
|
evidenceType: EvidenceType
|
|
|
|
|
fileId: number | string | null
|
|
|
|
|
fileName: string | null
|
|
|
|
|
sealNo: string | null
|
|
|
|
|
remark: string | null
|
|
|
|
|
version: number
|
2026-08-10 12:05:20 +08:00
|
|
|
status: 'ACTIVE' | 'VOIDED' | string
|
2026-08-06 13:56:04 +08:00
|
|
|
createdAt: string | null
|
|
|
|
|
updatedAt: string | null
|
2026-08-10 12:05:20 +08:00
|
|
|
voidReason?: string | null
|
|
|
|
|
voidedBy?: number | string | null
|
|
|
|
|
voidedByName?: string | null
|
|
|
|
|
voidedAt?: string | null
|
2026-08-06 13:56:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface EvidenceList {
|
|
|
|
|
inboundId: number | string
|
|
|
|
|
evidences: Evidence[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LoadingActionResponse {
|
|
|
|
|
mainStatus: string
|
|
|
|
|
evidences: EvidenceList | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PickingRequest {
|
|
|
|
|
expectedVersion: number
|
|
|
|
|
expectedCount: number
|
|
|
|
|
pickedCount: number
|
|
|
|
|
remark?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SmartSplitRequest {
|
|
|
|
|
expectedVersion: number
|
|
|
|
|
items: PreloadItem[]
|
|
|
|
|
remark?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ConfirmPreloadRequest {
|
|
|
|
|
expectedVersion: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface UpsertEvidenceRequest {
|
|
|
|
|
expectedVersion: number
|
|
|
|
|
evidenceType: EvidenceType
|
|
|
|
|
fileId?: number | string
|
|
|
|
|
fileName?: string
|
|
|
|
|
sealNo?: string
|
|
|
|
|
remark?: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 12:05:20 +08:00
|
|
|
export interface UploadEvidenceResult {
|
|
|
|
|
fileId: number | string
|
|
|
|
|
fileName: string | null
|
|
|
|
|
contentType?: string | null
|
|
|
|
|
sizeBytes?: number | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface VoidEvidenceRequest {
|
|
|
|
|
expectedVersion: number
|
|
|
|
|
reason: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 13:56:04 +08:00
|
|
|
export interface CompleteLoadingRequest {
|
|
|
|
|
expectedVersion: number
|
|
|
|
|
remark?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function query(tenantId: string | number) {
|
|
|
|
|
return { tenantId: String(tenantId) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function idempotencyKey(operation: string, inboundId: string | number) {
|
|
|
|
|
return `admin-${operation}-${inboundId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function writeOptions(operation: string, inboundId: string | number, tenantId: string | number) {
|
|
|
|
|
return { params: query(tenantId), headers: { 'Idempotency-Key': idempotencyKey(operation, inboundId) } }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getPreload(tenantId: string | number, inboundId: string | number) {
|
|
|
|
|
const response = await http.get<ApiEnvelope<PreloadPlan>, ApiEnvelope<PreloadPlan>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/preload`, { params: query(tenantId) },
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 12:05:20 +08:00
|
|
|
export async function getOutboundDispatch(tenantId: string | number, orderId?: string | number, inboundId?: string | number) {
|
|
|
|
|
const response = await http.get<ApiEnvelope<OutboundDispatchAggregate[]>, ApiEnvelope<OutboundDispatchAggregate[]>>(
|
|
|
|
|
'/platform/v1/first-leg/outbound-dispatches',
|
|
|
|
|
{ params: { tenantId: String(tenantId), ...(orderId ? { orderId: String(orderId) } : {}), ...(inboundId ? { inboundId: String(inboundId) } : {}) } },
|
|
|
|
|
)
|
|
|
|
|
const result = unwrapApiResponse(response)
|
|
|
|
|
return result?.[0] || null
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 13:56:04 +08:00
|
|
|
export async function smartSplit(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
request: SmartSplitRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<SplitActionResponse>, ApiEnvelope<SplitActionResponse>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/split/smart`, request, writeOptions('smart-split', inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function confirmingPreload(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
request: ConfirmPreloadRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<SplitActionResponse>, ApiEnvelope<SplitActionResponse>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/preload/confirming`, request,
|
|
|
|
|
writeOptions('preload-confirming', inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function confirmPreload(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
request: ConfirmPreloadRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<SplitActionResponse>, ApiEnvelope<SplitActionResponse>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/preload/confirm`, request,
|
|
|
|
|
writeOptions('preload-confirm', inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getPicking(tenantId: string | number, inboundId: string | number) {
|
|
|
|
|
const response = await http.get<ApiEnvelope<PickingTask>, ApiEnvelope<PickingTask>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/loading/picking`, { params: query(tenantId) },
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function startPicking(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
request: PickingRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<PickingTask>, ApiEnvelope<PickingTask>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/loading/picking/start`, request,
|
|
|
|
|
writeOptions('picking-start', inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function completePicking(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
request: PickingRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<PickingTask>, ApiEnvelope<PickingTask>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/loading/picking/complete`, request,
|
|
|
|
|
writeOptions('picking-complete', inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getEvidence(tenantId: string | number, inboundId: string | number) {
|
|
|
|
|
const response = await http.get<ApiEnvelope<EvidenceList>, ApiEnvelope<EvidenceList>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/loading/evidence`, { params: query(tenantId) },
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function upsertEvidence(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
request: UpsertEvidenceRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<LoadingActionResponse>, ApiEnvelope<LoadingActionResponse>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/loading/evidence`, request,
|
|
|
|
|
writeOptions(`evidence-${request.evidenceType}`, inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 12:05:20 +08:00
|
|
|
/** 真实 File 服务上传;返回的 fileId 才能提交证据,不接受本地文件名冒充。 */
|
|
|
|
|
export async function uploadEvidenceFile(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
evidenceType: EvidenceType,
|
|
|
|
|
file: File,
|
|
|
|
|
) {
|
|
|
|
|
const body = new FormData()
|
|
|
|
|
body.append('evidenceType', evidenceType)
|
|
|
|
|
body.append('file', file)
|
|
|
|
|
const response = await http.post<ApiEnvelope<UploadEvidenceResult>, ApiEnvelope<UploadEvidenceResult>>(
|
|
|
|
|
`/platform/v1/files/loading-evidence/${tenantId}/${inboundId}`,
|
|
|
|
|
body,
|
|
|
|
|
{ headers: { 'Content-Type': 'multipart/form-data' } },
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function voidEvidence(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
evidenceId: string | number,
|
|
|
|
|
request: VoidEvidenceRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<LoadingActionResponse>, ApiEnvelope<LoadingActionResponse>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/loading/evidence/${evidenceId}/void`,
|
|
|
|
|
request,
|
|
|
|
|
writeOptions('evidence-void', inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 13:56:04 +08:00
|
|
|
export async function completeLoading(
|
|
|
|
|
tenantId: string | number,
|
|
|
|
|
inboundId: string | number,
|
|
|
|
|
request: CompleteLoadingRequest,
|
|
|
|
|
) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<LoadingActionResponse>, ApiEnvelope<LoadingActionResponse>>(
|
|
|
|
|
`${inboundBasePath}/${inboundId}/loading/complete`, request,
|
|
|
|
|
writeOptions('loading-complete', inboundId, tenantId),
|
|
|
|
|
)
|
|
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|