mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 02:30:55 +00:00
mile
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
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[];
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import { TrainSchedulingManage, TrainSchedulingView } from '../../common/booking
|
||||
|
||||
import { CreateLastMileDto } from './dto/create-last-mile.dto';
|
||||
import { UpdateLastMileDto } from './dto/update-last-mile.dto';
|
||||
import { AllocateLastMileContainersDto } from './dto/allocate-containers.dto';
|
||||
import { SetVehiclesDto } from './dto/set-vehicles.dto';
|
||||
import { SetDistancesDto } from './dto/set-distances.dto';
|
||||
import { LastMileStatus } from './entities/last-mile.entity';
|
||||
@@ -93,15 +92,6 @@ export class LastMileController {
|
||||
return this.lastMileService.remove(id);
|
||||
}
|
||||
|
||||
@Post(':id/allocate-containers')
|
||||
@TrainSchedulingManage()
|
||||
@ApiOperation({ summary: 'Allocate containers to vehicles' })
|
||||
async allocateContainers(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: AllocateLastMileContainersDto,
|
||||
) {
|
||||
return this.lastMileService.allocateContainers(id, dto.allocations);
|
||||
}
|
||||
|
||||
@Post(':id/vehicles')
|
||||
@TrainSchedulingManage()
|
||||
|
||||
@@ -243,14 +243,16 @@ export class LastMileService {
|
||||
return record;
|
||||
}
|
||||
|
||||
@OnEvent("lastmile.invoice.paid")
|
||||
@OnEvent("last_mile.invoice.paid")
|
||||
async onBookingInvoicePaid(payload: InvoiceEventPayload): Promise<void> {
|
||||
try {
|
||||
await this.lastMileRepository.update(payload.sourceId, { paid: true } as any);
|
||||
this.logger.log(`Marked last-mile record ${payload.sourceId} as paid (invoice ${payload.invoiceId})`);
|
||||
// Invoice paid → the delivery is complete. Route through update() so it
|
||||
// also frees the trucks + records history (same as "Mark Delivered").
|
||||
await this.update(payload.sourceId, { status: 'DELIVERED', paid: true } as unknown as UpdateLastMileDto);
|
||||
this.logger.log(`Last-mile ${payload.sourceId} marked DELIVERED on invoice ${payload.invoiceId} payment`);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Failed to update last-mile payment status for record ${payload.sourceId}: ${String(err)}`,
|
||||
`Failed to deliver last-mile ${payload.sourceId} on payment: ${String(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -484,6 +486,15 @@ export class LastMileService {
|
||||
remainingPayment?: number,
|
||||
): Promise<LastMile> {
|
||||
await this.findById(id);
|
||||
|
||||
// Distances are locked once the invoice exists.
|
||||
const invoices = await this.billing.findBySourceIds('last_mile', [id]);
|
||||
if (invoices.length) {
|
||||
throw new BadRequestException(
|
||||
'Distances cannot be changed after the invoice is generated',
|
||||
);
|
||||
}
|
||||
|
||||
for (const d of distances) {
|
||||
await this.dataSource.manager.update(
|
||||
LastMileVehicleAssignment,
|
||||
@@ -541,6 +552,14 @@ export class LastMileService {
|
||||
async remove(id: string): Promise<void> {
|
||||
const existing = await this.findById(id);
|
||||
|
||||
// Can't delete once billed.
|
||||
const invoices = await this.billing.findBySourceIds('last_mile', [id]);
|
||||
if (invoices.length) {
|
||||
throw new BadRequestException(
|
||||
'Cannot delete a last-mile delivery after its invoice is generated',
|
||||
);
|
||||
}
|
||||
|
||||
// Every vehicle this delivery holds — junction + legacy + container rows.
|
||||
const assignments = await this.dataSource.manager.find(LastMileVehicleAssignment, {
|
||||
where: { lastMileId: id },
|
||||
@@ -581,89 +600,4 @@ export class LastMileService {
|
||||
}
|
||||
}
|
||||
|
||||
async allocateContainers(
|
||||
lastMileId: string,
|
||||
allocations: Array<{ containerId: string; vehicleId: string }>,
|
||||
) {
|
||||
const lastMile = await this.findById(lastMileId);
|
||||
if (!lastMile) {
|
||||
throw new NotFoundException(`Last-mile record ${lastMileId} not found`);
|
||||
}
|
||||
|
||||
// Capture the vehicles currently on these containers so a reallocation can
|
||||
// be diffed into assigned/released history events below.
|
||||
const previousAllocations = await this.dataSource.manager.find(
|
||||
LastMileContainerAllocation,
|
||||
{
|
||||
where: {
|
||||
lastMileId,
|
||||
containerId: In(allocations.map((a) => a.containerId)),
|
||||
},
|
||||
},
|
||||
);
|
||||
const previousVehicleIds = previousAllocations
|
||||
.map((a) => a.vehicleId)
|
||||
.filter((id): id is string => Boolean(id));
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
for (const allocation of allocations) {
|
||||
await manager.delete(LastMileContainerAllocation, {
|
||||
lastMileId,
|
||||
containerId: allocation.containerId,
|
||||
});
|
||||
await manager.insert(LastMileContainerAllocation, {
|
||||
lastMileId,
|
||||
containerId: allocation.containerId,
|
||||
vehicleId: allocation.vehicleId,
|
||||
containerType: 'CONTAINER',
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Keep vehicle availability in sync: newly-allocated cars go BUSY, cars no
|
||||
// longer on any of these containers are freed if unused elsewhere.
|
||||
const vehicleIds = new Set(allocations.map((a) => a.vehicleId));
|
||||
await Promise.all(
|
||||
[...vehicleIds].map((id) =>
|
||||
this.vehiclesService.setAvailability(id, VehicleAvailability.BUSY),
|
||||
),
|
||||
);
|
||||
await this.vehiclesService.releaseIfUnused(
|
||||
previousVehicleIds.filter((id) => !vehicleIds.has(id)),
|
||||
);
|
||||
|
||||
// History: one event per vehicle actually added or removed by this
|
||||
// multi-car (re)allocation, so reassignments show on every timeline.
|
||||
const prevSet = new Set(previousVehicleIds);
|
||||
const bookingRef = await this.resolveBookingRef(lastMile);
|
||||
for (const vehicleId of vehicleIds) {
|
||||
if (prevSet.has(vehicleId)) continue;
|
||||
const info = await this.vehicleInfo(vehicleId);
|
||||
await this.history.record({
|
||||
eventType: FleetEventType.MILE_VEHICLE_ASSIGNED,
|
||||
vehicleId,
|
||||
lastMileId,
|
||||
driverId: info.driverId,
|
||||
label: lastMile.status,
|
||||
metadata: { mile: 'LAST', bookingRef, vehiclePlate: info.plate, driverName: info.driverName },
|
||||
});
|
||||
}
|
||||
for (const vehicleId of previousVehicleIds) {
|
||||
if (vehicleIds.has(vehicleId)) continue;
|
||||
const info = await this.vehicleInfo(vehicleId);
|
||||
await this.history.record({
|
||||
eventType: FleetEventType.MILE_VEHICLE_RELEASED,
|
||||
vehicleId,
|
||||
lastMileId,
|
||||
driverId: info.driverId,
|
||||
metadata: { mile: 'LAST', bookingRef, vehiclePlate: info.plate, driverName: info.driverName },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
allocated: allocations.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user