import { projectWarehouseArrival } from '@/modules/container-loading' import type { CalendarSources, ScheduleRange, WarehouseArrival } from './scheduleApi' export const scheduleTypes = ['arrival', 'dispatch', 'return', 'warehouse'] as const export type ScheduleType = typeof scheduleTypes[number] export const scheduleLabels: Record = { arrival: '到港', dispatch: '排车', return: '还柜', warehouse: '计划到仓' } export interface ScheduleEvent { key: string type: ScheduleType date: string | null time: string title: string subtitle?: string fields: Array<{ label: string; value?: string | null }> sourcePath?: string warehouse?: WarehouseArrival canEdit?: boolean } const inRange = (date: string | null, range: ScheduleRange) => !date || date >= range.from && date < range.to const timestamp = (at: string | null) => at?.replace('T', ' ').slice(0, 16) || '—' export function projectContainerSchedule(sources: CalendarSources, warehouses: WarehouseArrival[] | null, range: ScheduleRange, canViewContainer: boolean) { const events: ScheduleEvent[] = [] const warehouseMap = new Map(warehouses?.map(row => [row.sourceContainerBookingId, row]) || []) const containerMap = new Map(sources.containers.map(row => [row.containerBookingId, row])) for (const row of sources.containers) { const label = row.cabinetNo || '—' const fields = [{ label: '集装箱号', value: row.containerNo }, { label: '柜次号', value: row.cabinetNo }, { label: '船名航次', value: [row.vesselName, row.voyageNo].filter(Boolean).join(' / ') }, { label: '目的港', value: row.destinationPortName }] const sourcePath = canViewContainer ? `/container-loading/overseas/${encodeURIComponent(row.containerBookingId)}` : undefined const date = row.arrivalAt?.slice(0, 10) || null if (inRange(date, range)) events.push({ key: `arrival-${row.containerBookingId}`, type: 'arrival', date, time: row.arrivalAt?.slice(11, 16) || '', title: `${label} 到港`, sourcePath, subtitle: row.arrivalAt ? row.arrivalType === 'ACTUAL' ? '实际到港' : '预计到港' : '到港日期未定', fields: [...fields, { label: row.arrivalType === 'ACTUAL' ? '实际到港' : '预计到港', value: timestamp(row.arrivalAt) }] }) // 读取失败时不能用默认值遮掉无法读取的人工安排。 if (warehouses === null) continue const warehouse = warehouseMap.get(row.containerBookingId) warehouseMap.delete(row.containerBookingId) const projected = projectWarehouseArrival(warehouse) const planned = projected.date if (inRange(planned, range)) events.push({ key: `warehouse-${row.containerBookingId}`, type: 'warehouse', date: planned, time: projected.time, title: `${label} 计划到仓`, sourcePath, warehouse, canEdit: warehouse?.canEdit === true, subtitle: !planned ? '柜内计划时间不一致,待核实' : warehouse && warehouse.planCount > 0 ? '已有入库计划,日期已冻结' : projected.isDefault ? '到港日期+2个自然日(默认)' : '人工维护', fields: [{ label: '集装箱号', value: row.containerNo }, { label: '接仓仓库', value: warehouse?.warehouseName || warehouse?.warehouseCode }, { label: '计划到仓', value: warehouse?.plannedAt ? timestamp(warehouse.plannedAt) : planned }, { label: '实际到仓', value: timestamp(warehouse?.actualAt || null) }] }) } // 人工改期可能跨月,与当前月份的到港记录无关。 for (const row of warehouseMap.values()) { const projected = projectWarehouseArrival(row) const date = projected.date if (!date || !inRange(date, range)) continue events.push({ key: `warehouse-${row.sourceContainerBookingId}`, type: 'warehouse', date, time: projected.time, title: `${row.cabinetNo || '—'} 计划到仓`, subtitle: row.planCount > 0 ? '已有入库计划,日期已冻结' : '人工维护', warehouse: row, canEdit: row.canEdit, sourcePath: canViewContainer ? `/container-loading/overseas/${encodeURIComponent(row.sourceContainerBookingId)}` : undefined, fields: [{ label: '集装箱号', value: row.containerNo }, { label: '接仓仓库', value: row.warehouseName || row.warehouseCode }, { label: '计划到仓', value: row.plannedAt ? timestamp(row.plannedAt) : date }, { label: '实际到仓', value: timestamp(row.actualAt) }] }) } for (const row of sources.emptyReturns) { const at = row.actualEmptyReturnAt || row.emptyReturnWarning.deadlineAt if (!at || !inRange(at.slice(0, 10), range)) continue events.push({ key: `return-${row.containerBookingId}`, type: 'return', date: at.slice(0, 10), time: at.slice(11, 16), title: `${containerMap.get(row.containerBookingId)?.cabinetNo || '—'} ${row.actualEmptyReturnAt ? '已还柜' : '还柜'}`, subtitle: row.actualEmptyReturnAt ? '实际还柜' : row.emptyReturnWarning.status === 'OVERDUE' ? '已超过还柜截止时间' : '还柜截止提醒', sourcePath: canViewContainer ? `/container-loading/overseas/${encodeURIComponent(row.containerBookingId)}` : undefined, fields: [{ label: '集装箱号', value: row.containerNo }, { label: '车队', value: row.fleetSupplierName }, { label: '还柜截止', value: timestamp(row.emptyReturnWarning.deadlineAt) }, { label: '实际还柜', value: timestamp(row.actualEmptyReturnAt) }] }) } return events }