60 lines
5.0 KiB
TypeScript
60 lines
5.0 KiB
TypeScript
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
|
import type { FbaAppointmentDto } from '@/modules/overseas-warehouse'
|
|
export interface BatchSourceAllocation {
|
|
customerId: string; sourceKey: string; destinationKey: string; packageCount: number | null
|
|
originalPackages: number | null; sourceVersion: string | null; cargoVerificationVersion: number | null; shipmentReferences: string[]
|
|
}
|
|
export interface FbaAppointmentBatchContext { appointment: FbaAppointmentDto; originalSourceRefs: Array<{ customerId: string; sourceKey: string; destinationKey: string }>; currentBatches: FbaAppointmentDto[]; sources: BatchSourceAllocation[]; allowedActions: Array<'VERIFY_CARGO' | 'SPLIT' | 'MERGE'>; blockReasons: string[] }
|
|
export interface BatchAllocation { customerId: string; sourceKey: string; packageCount: number; expectedSourceVersion: string | null; expectedVerificationVersion: number | null }
|
|
export interface RepartitionFbaAppointmentsRequest {
|
|
originals: Array<{ appointmentNo: string; version: number }>
|
|
batches: Array<{ plannedDate: string | null; remark: string | null; allocations: BatchAllocation[] }>
|
|
reason: string; idempotencyKey: string
|
|
}
|
|
export interface RepartitionFbaAppointmentsResponse { replacedBatchKeys: string[]; replacedAppointmentNos: string[]; appointments: FbaAppointmentDto[] }
|
|
const base = '/platform/v1/last-leg/appointment-planning'
|
|
export async function getAppointmentBatch(batchKey: string) {
|
|
return unwrapApiResponse(await http.get<ApiEnvelope<FbaAppointmentBatchContext>, ApiEnvelope<FbaAppointmentBatchContext>>(`${base}/batches/${encodeURIComponent(batchKey)}`))
|
|
}
|
|
export async function repartitionAppointments(request: RepartitionFbaAppointmentsRequest) {
|
|
return unwrapApiResponse(await http.post<ApiEnvelope<RepartitionFbaAppointmentsResponse>, ApiEnvelope<RepartitionFbaAppointmentsResponse>>(`${base}/appointments/repartition`, request))
|
|
}
|
|
export const allocationKey = (source: Pick<BatchSourceAllocation, 'customerId' | 'sourceKey' | 'destinationKey'>) => JSON.stringify([source.customerId, source.sourceKey, source.destinationKey])
|
|
export function mergeBatchSources(contexts: FbaAppointmentBatchContext[]): BatchSourceAllocation[] {
|
|
const first = contexts[0]
|
|
if (!first) throw new Error('请先选择批次')
|
|
const rows = new Map<string, BatchSourceAllocation>()
|
|
const seen = new Set<string>()
|
|
for (const context of contexts) {
|
|
const appointment = context.appointment
|
|
if (seen.has(appointment.appointmentNo)) throw new Error('同一批次不能重复选择')
|
|
seen.add(appointment.appointmentNo)
|
|
if (appointment.originWarehouseId !== first.appointment.originWarehouseId || appointment.fbaWarehouseCode !== first.appointment.fbaWarehouseCode) throw new Error('只允许同一起运仓、同一目的仓的批次合并')
|
|
if (appointment.replacementBatchKeys?.length || appointment.replacementAppointmentNos?.length) throw new Error('原批次已被替代,请选择当前有效批次')
|
|
if (!context.sources.length) throw new Error('批次缺少真实货物来源,请核对后再操作')
|
|
for (const source of context.sources) {
|
|
if (source.packageCount == null || !Number.isSafeInteger(Number(source.packageCount)) || Number(source.packageCount) < 1) throw new Error('请先核实每个来源的本批箱数')
|
|
const key = allocationKey(source), previous = rows.get(key)
|
|
if (previous && (previous.sourceVersion !== source.sourceVersion || previous.cargoVerificationVersion !== source.cargoVerificationVersion || JSON.stringify(previous.shipmentReferences) !== JSON.stringify(source.shipmentReferences))) throw new Error('同一来源的核实版本或货件资料不一致,请重新读取批次')
|
|
const count = Number(source.packageCount) + (previous?.packageCount || 0)
|
|
if (!Number.isSafeInteger(count)) throw new Error('来源箱数超出可处理范围')
|
|
rows.set(key, { ...source, packageCount: count })
|
|
}
|
|
}
|
|
return [...rows.values()]
|
|
}
|
|
export function allocation(source: BatchSourceAllocation, packages: number): BatchAllocation {
|
|
return { customerId: source.customerId, sourceKey: source.sourceKey, packageCount: packages, expectedSourceVersion: source.sourceVersion, expectedVerificationVersion: source.cargoVerificationVersion }
|
|
}
|
|
export function splitBatch(context: FbaAppointmentBatchContext, moved: Record<string, number | string>): [BatchAllocation[], BatchAllocation[]] {
|
|
const remaining: BatchAllocation[] = [], next: BatchAllocation[] = []
|
|
for (const source of mergeBatchSources([context])) {
|
|
const quantity = Number(moved[allocationKey(source)] ?? 0), total = Number(source.packageCount)
|
|
if (!Number.isSafeInteger(quantity) || quantity < 0 || quantity > total) throw new Error('每票分出箱数须为0至本批箱数之间的整数')
|
|
if (quantity > 0) next.push(allocation(source, quantity))
|
|
if (quantity < total) remaining.push(allocation(source, total - quantity))
|
|
}
|
|
if (!remaining.length || !next.length) throw new Error('两批都须保留至少一箱货物')
|
|
return [remaining, next]
|
|
}
|