diff --git a/apps/edr-freight-api/src/modules/last-mile/dto/allocate-containers.dto.ts b/apps/edr-freight-api/src/modules/last-mile/dto/allocate-containers.dto.ts index de86ac883..7fff8247e 100644 --- a/apps/edr-freight-api/src/modules/last-mile/dto/allocate-containers.dto.ts +++ b/apps/edr-freight-api/src/modules/last-mile/dto/allocate-containers.dto.ts @@ -1,8 +1,17 @@ +import { IsArray, IsUUID, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; + export class LastMileContainerAllocationDto { + @IsUUID() containerId!: string; + + @IsUUID() vehicleId!: string; } export class AllocateLastMileContainersDto { + @IsArray() + @ValidateNested({ each: true }) + @Type(() => LastMileContainerAllocationDto) allocations!: LastMileContainerAllocationDto[]; } diff --git a/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts b/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts index 744aa7df1..201692fdb 100644 --- a/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts +++ b/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts @@ -150,7 +150,7 @@ export class LastMileService { const [data, total] = await this.lastMileRepository.findAndCount({ where, relations: { - booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true, bookingContainers: true }, + booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true, bookingContainers: { containerType: true } }, vehicle: true, vehicleAssignments: { vehicle: true }, }, @@ -173,7 +173,7 @@ export class LastMileService { async findById(id: string): Promise { const record = await this.lastMileRepository.findById(id, { relations: { - booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true, bookingContainers: true }, + booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true, bookingContainers: { containerType: true } }, vehicle: true, vehicleAssignments: { vehicle: true }, }, diff --git a/apps/edr-freight-web/backoffice/src/components/LastMileContainerAllocationTable.tsx b/apps/edr-freight-web/backoffice/src/components/LastMileContainerAllocationTable.tsx index cc619cb9a..02b62ec4c 100644 --- a/apps/edr-freight-web/backoffice/src/components/LastMileContainerAllocationTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/LastMileContainerAllocationTable.tsx @@ -22,8 +22,10 @@ export interface LastMileContainerRow { qty: number; } +/** One vehicle (with trailer) carries at most this many containers. */ +const CONTAINERS_PER_VEHICLE = 2; + export interface LastMileContainerAllocationTableProps { - lastMileId: string; containers: LastMileContainerRow[]; onSave: (allocations: Array<{ containerId: string; vehicleId: string }>) => Promise; } @@ -33,7 +35,6 @@ export interface LastMileContainerAllocationTableProps { * Displays containers with type/qty, vehicle dropdown per row, and save action. */ export function LastMileContainerAllocationTable({ - lastMileId, containers, onSave, }: LastMileContainerAllocationTableProps) { @@ -43,7 +44,8 @@ export function LastMileContainerAllocationTable({ const { data: vehicles = [], isLoading: vehiclesLoading } = useQuery({ queryKey: ["vehicles", "free"], - queryFn: () => vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" }), + queryFn: () => + vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" }).then((r) => r.data), }); const vehicleOptions = useMemo( @@ -85,13 +87,32 @@ export function LastMileContainerAllocationTable({ }); const allocatedCount = Object.values(allocations).filter(Boolean).length; - const allAllocated = allocatedCount === containers.length; + + // Containers already loaded onto each vehicle, to enforce the 2-per-vehicle cap. + const loadByVehicle = useMemo(() => { + const map: Record = {}; + for (const c of containers) { + const v = allocations[c.id]; + if (v) map[v] = (map[v] ?? 0) + (c.qty || 1); + } + return map; + }, [allocations, containers]); + + /** Options for a given row: a vehicle is disabled if assigning this container + * to it would exceed its 2-container capacity. */ + const optionsForRow = (row: LastMileContainerRow) => + vehicleOptions.map((o) => { + const already = loadByVehicle[o.value] ?? 0; + const selfHere = allocations[row.id] === o.value ? row.qty || 1 : 0; + const over = already - selfHere + (row.qty || 1) > CONTAINERS_PER_VEHICLE; + return { ...o, disabled: over }; + }); if (vehiclesLoading) { return ( - + - + ); } @@ -126,7 +147,7 @@ export function LastMileContainerAllocationTable({