Files
admin-web/src/modules/appointment-calendar/batchApi.ts
T

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]
}