73 lines
5.3 KiB
TypeScript
73 lines
5.3 KiB
TypeScript
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<ScheduleType, string> = { 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
|
|
}
|