Merge pull request #905 from Tria-plc/testfixes

Fixes
Download Excel template with instructions
Parse uploaded file, preview trucks
Validate container assignments (1x40ft OR 2x20ft per truck)
Commit bulk upload in one call
This commit is contained in:
Hagernesh Tadesse
2026-07-22 13:54:59 +03:00
committed by GitHub
11 changed files with 464 additions and 11 deletions

View File

@@ -480,6 +480,20 @@ export class BookingsController {
return this.customerTruckService.addTruck(id, dto);
}
@Post(':id/customer-trucks/bulk')
@ApiOperation({ summary: 'Bulk add customer trucks from array payload (Excel parsed)' })
async bulkAddCustomerTrucks(
@Param('id', ParseUUIDPipe) id: string,
@Body() payload: { trucks: AddCustomerTruckDto[] },
@CurrentUser() user: TCurrentUser,
) {
const booking = await this.bookingsService.findById(id);
if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
await this.bookingsService.assertCustomerCanAccessBooking(user?.id, booking);
}
return this.customerTruckService.addBulkTrucks(id, payload.trucks);
}
@Patch(':id/customer-trucks/:assignmentId')
@ApiOperation({ summary: 'Edit a not-yet-arrived customer truck (plate/driver/type + containers)' })
async updateCustomerTruck(

View File

@@ -576,4 +576,35 @@ export class CustomerTruckService {
}
/** Contract container sizes (e.g. "20ft" / "40ft") for the given container numbers. */
async addBulkTrucks(
bookingId: string,
dtos: AddCustomerTruckDto[],
): Promise<{
success: number;
failed: number;
errors: Array<{ row: number; truck: string; reason: string }>;
}> {
const errors: Array<{ row: number; truck: string; reason: string }> = [];
let successCount = 0;
for (let i = 0; i < dtos.length; i++) {
try {
await this.addTruck(bookingId, dtos[i]);
successCount++;
} catch (err: any) {
errors.push({
row: i + 2, // Row 1 is header
truck: dtos[i].truckPlateNumber,
reason: err.message || 'Unknown error',
});
}
}
return {
success: successCount,
failed: errors.length,
errors,
};
}
}

View File

@@ -0,0 +1,48 @@
import { IsString, IsNotEmpty, IsIn, IsArray, ArrayMaxSize, ArrayUnique, Matches, IsOptional } from 'class-validator';
import { CUSTOMER_TRUCK_TYPES } from './customer-truck-assignment.dto';
export class BulkCustomerTruckRow {
@IsString()
@IsNotEmpty()
truckPlateNumber!: string;
@IsString()
@IsNotEmpty()
driverName!: string;
@IsString()
@IsNotEmpty()
@IsIn(CUSTOMER_TRUCK_TYPES)
truckType!: string;
@IsOptional()
@IsArray()
@ArrayMaxSize(2)
@ArrayUnique()
@Matches(/^[A-Z]{4}\d{7}$/, {
each: true,
message: 'each container must be ISO format (e.g. ABCD1234567)',
})
containerNumbers?: (string | null)[];
}
export class BulkCustomerTrucksDto {
@IsArray()
@ArrayMaxSize(100)
trucks!: BulkCustomerTruckRow[];
}
export interface BulkTruckUploadResult {
success: number;
failed: number;
errors: Array<{
row: number;
truck: string;
reason: string;
}>;
created: Array<{
truckPlateNumber: string;
driverName: string;
containers: number;
}>;
}

View File

@@ -2811,13 +2811,12 @@ export class TrainSchedulingService {
return [
`<tr class="empty">
${wagonCells}
<td colspan="6">EMPTY — no cargo allocated</td>
<td colspan="4">EMPTY — no cargo allocated</td>
</tr>`,
];
}
return allocations.map((allocation) => {
const booking = allocation.booking ?? bookingById.get(allocation.bookingId);
const company = booking?.company as Record<string, unknown> | null | undefined;
const cargoType = (booking as unknown as { cargoType?: { name?: string; code?: string } } | undefined)?.cargoType;
const containerItems = allocation.containerItems ?? [];
const firstContainer = containerItems[0];
@@ -2826,8 +2825,6 @@ export class TrainSchedulingService {
const chassisNumbers = containerItems.map((item) => item.chassisNumber).filter(Boolean).join(', ');
return `<tr>
${wagonCells}
<td>${esc(company?.name ?? company?.legalName ?? company?.tradeName ?? booking?.companyId)}</td>
<td>${esc(booking?.companyId)}</td>
<td>${esc(cargoType?.name ?? cargoType?.code ?? allocation.loadType)}</td>
<td>${esc(containerNumbers || firstContainer?.containerNumber)}</td>
<td>${esc(chassisNumbers)}</td>
@@ -2909,8 +2906,6 @@ export class TrainSchedulingService {
<th class="num">Equated Length</th>
<th class="num">Tare Weight</th>
<th class="num">Load Capacity</th>
<th>Customer Name</th>
<th>Customer ID</th>
<th>Cargo Type</th>
<th>Container No</th>
<th>Chassis No</th>
@@ -2918,7 +2913,7 @@ export class TrainSchedulingService {
</tr>
</thead>
<tbody>
${rows || '<tr><td colspan="12">No wagons on this train set.</td></tr>'}
${rows || '<tr><td colspan="10">No wagons on this train set.</td></tr>'}
</tbody>
</table>