This commit is contained in:
natib21
2026-07-03 20:21:16 +00:00
parent 5d1a2aa319
commit 16bd7fabcc
7 changed files with 191 additions and 38 deletions

View File

@@ -0,0 +1,24 @@
import { IsArray, IsNumber, IsOptional, IsUUID, Min, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class VehicleDistanceInput {
@IsUUID()
vehicleId!: string;
@IsNumber()
@Min(0)
distanceKm!: number;
}
/** Per-vehicle actual distances for a last-mile delivery (multi-truck). */
export class SetDistancesDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => VehicleDistanceInput)
distances!: VehicleDistanceInput[];
/** Recomputed remaining payment (total km × rate), from the client. */
@IsOptional()
@IsNumber()
remainingPayment?: number;
}

View File

@@ -32,4 +32,8 @@ export class LastMileVehicleAssignment extends BaseEntity {
* number when known, else entered manually at assignment time. */
@Column({ name: 'container_number', type: 'varchar', nullable: true })
containerNumber?: string | null;
/** Actual distance driven by this truck (km), entered per vehicle. */
@Column({ name: 'distance_km', type: 'numeric', precision: 10, scale: 2, nullable: true })
distanceKm?: number | null;
}

View File

@@ -19,6 +19,7 @@ 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';
import { LastMileService } from './last-mile.service';
import { LastMileInvoiceService } from './last-mile-invoice.service';
@@ -147,4 +148,22 @@ export class LastMileController {
) {
return this.lastMileService.setVehicles(id, dto.vehicles);
}
@Post(':id/distances')
@TrainSchedulingManage()
@ApiOperation({ summary: 'Set per-vehicle actual distances (does not generate an invoice)' })
async setDistances(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: SetDistancesDto,
) {
return this.lastMileService.setDistances(id, dto.distances, dto.remainingPayment);
}
@Post(':id/invoice')
@TrainSchedulingManage()
@ApiOperation({ summary: 'Generate the delivery-fee invoice for a last-mile leg' })
async generateInvoice(@Param('id', ParseUUIDPipe) id: string) {
const record = await this.lastMileService.findById(id);
return this.lastMileInvoiceService.ensureInvoiceFor(record);
}
}

View File

@@ -449,6 +449,32 @@ export class LastMileService {
return this.findById(id);
}
/**
* Record each truck's actual distance. The delivery total (exact_km) is their
* sum and drives billing; `remainingPayment` (total km × rate) is recomputed
* client-side. Does NOT generate an invoice — that's a separate explicit step.
*/
async setDistances(
id: string,
distances: Array<{ vehicleId: string; distanceKm: number }>,
remainingPayment?: number,
): Promise<LastMile> {
await this.findById(id);
for (const d of distances) {
await this.dataSource.manager.update(
LastMileVehicleAssignment,
{ lastMileId: id, vehicleId: d.vehicleId },
{ distanceKm: d.distanceKm },
);
}
const total = distances.reduce((s, d) => s + (Number(d.distanceKm) || 0), 0);
await this.lastMileRepository.update(id, {
exactKm: total,
...(remainingPayment != null ? { remainingPayment } : {}),
} as any);
return this.findById(id);
}
private async notifyDriverAssignment(vehicleId: string, record: LastMile): Promise<void> {
try {
const vehicle = await this.vehiclesService.findById(vehicleId);