diff --git a/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts b/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts index 0fe16be56..266a00044 100644 --- a/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts +++ b/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts @@ -276,7 +276,8 @@ export class ContractsController { if ( !hasFreightPermission(user, FREIGHT_PERMS.bookings.view) && !hasFreightPermission(user, FREIGHT_PERMS.bookings.clearanceView) && - !hasFreightPermission(user, FREIGHT_PERMS.bookings.reviewDocuments) + !hasFreightPermission(user, FREIGHT_PERMS.bookings.reviewDocuments) && + !hasFreightPermission(user, FREIGHT_PERMS.contracts.view) ) { await this.contractsService.assertCustomerCanAccessContract(user?.id, contract); } @@ -478,7 +479,10 @@ export class ContractsController { @CurrentUser() user: TCurrentUser, ) { const contract = await this.contractsService.findById(id); - if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) { + if ( + !hasFreightPermission(user, FREIGHT_PERMS.bookings.view) && + !hasFreightPermission(user, FREIGHT_PERMS.contracts.view) + ) { await this.contractsService.assertCustomerCanAccessContract(user?.id, contract); } const { view, html, signatures } = @@ -513,7 +517,10 @@ export class ContractsController { @Res() res: Response, ): Promise { const contract = await this.contractsService.findById(id); - if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) { + if ( + !hasFreightPermission(user, FREIGHT_PERMS.bookings.view) && + !hasFreightPermission(user, FREIGHT_PERMS.contracts.view) + ) { await this.contractsService.assertCustomerCanAccessContract(user?.id, contract); } const { stream, record } = await this.transitionService.streamContractPdf(id); @@ -569,9 +576,12 @@ export class ContractsController { @CurrentUser() user: TCurrentUser, ) { // H12(c): a customer may only renew a contract their company owns. Staff - // with bookings.view bypass, mirroring getContractView/downloadContractDocument. + // with bookings.view/contracts.view bypass, mirroring getContractView/downloadContractDocument. const contract = await this.contractsService.findById(id); - if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) { + if ( + !hasFreightPermission(user, FREIGHT_PERMS.bookings.view) && + !hasFreightPermission(user, FREIGHT_PERMS.contracts.view) + ) { await this.contractsService.assertCustomerCanAccessContract(user?.id, contract); } return this.transitionService.renew(id, resolveAuthUserId(user)); @@ -595,9 +605,12 @@ export class ContractsController { @UploadedFiles() files: Express.Multer.File[], ) { // H12(c): only the owning company's customer may upload clearance docs. - // Staff with bookings.view bypass, mirroring the other contract handlers. + // Staff with bookings.view/contracts.view bypass, mirroring the other contract handlers. const contract = await this.contractsService.findById(id); - if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) { + if ( + !hasFreightPermission(user, FREIGHT_PERMS.bookings.view) && + !hasFreightPermission(user, FREIGHT_PERMS.contracts.view) + ) { await this.contractsService.assertCustomerCanAccessContract(user?.id, contract); } return this.clearanceService.uploadDocuments(id, files ?? []); diff --git a/apps/edr-freight-api/src/modules/train-scheduling/train-capacity.util.ts b/apps/edr-freight-api/src/modules/train-scheduling/train-capacity.util.ts index 463ae7ea8..3e4fab0d3 100644 --- a/apps/edr-freight-api/src/modules/train-scheduling/train-capacity.util.ts +++ b/apps/edr-freight-api/src/modules/train-scheduling/train-capacity.util.ts @@ -253,12 +253,18 @@ export function minLocomotiveLimits( maxTrainLengthMeters: Math.min( ...locomotives.map((l) => num(l.maxTrainLengthMeters, Infinity) || Infinity), ), - // Weakest locomotive's tolerance governs the set, same as its caps. - overageToleranceTons: Math.min(...locomotives.map((l) => num(l.overageToleranceTons))), - overageToleranceMeters: Math.min(...locomotives.map((l) => num(l.overageToleranceMeters))), + // Weakest CONFIGURED tolerance governs the set — a locomotive with no + // tolerance set has no opinion, it does not zero out the others. + overageToleranceTons: minConfigured(locomotives.map((l) => l.overageToleranceTons)), + overageToleranceMeters: minConfigured(locomotives.map((l) => l.overageToleranceMeters)), }; } +function minConfigured(values: Array): number { + const configured = values.filter((v) => v != null).map((v) => num(v)); + return configured.length ? Math.min(...configured) : 0; +} + /** Per-booking train length from wagon count and freight-specific wagon type length. */ export function bookingTrainLengthMeters( freightType: string | null | undefined,