From b5cf05ebde857a149f6413901ed0ac1211582539 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:44:20 -0400 Subject: [PATCH 01/58] feat: ORV2-2973 - Application Queue resubmit and rejection (#1592) --- .../case-activity-comment.constraint.ts | 6 +- .../case-management.service.ts | 97 ++++++++++++------- .../dto/response/read-case-activity.dto.ts | 37 +++++++ .../profiles/case-management.profile.ts | 35 ++++++- .../application/application.service.ts | 12 +++ .../dto/response/read-application.dto.ts | 9 ++ .../profile/application.profile.ts | 16 +++ 7 files changed, 175 insertions(+), 37 deletions(-) create mode 100644 vehicles/src/modules/case-management/dto/response/read-case-activity.dto.ts diff --git a/vehicles/src/common/constraint/case-activity-comment.constraint.ts b/vehicles/src/common/constraint/case-activity-comment.constraint.ts index 9027d972d..2c7420340 100644 --- a/vehicles/src/common/constraint/case-activity-comment.constraint.ts +++ b/vehicles/src/common/constraint/case-activity-comment.constraint.ts @@ -16,8 +16,8 @@ export class CaseActivityCommentConstraint } ).caseActivityType; // Access the searchString property from the same object - // If CaseActivityType.APPROVED or CaseActivityType.REJECTED , comment should exists - if (caseActivityType !== CaseActivityType.WITHDRAWN && !comment) { + // If CaseActivityType.REJECTED, comment should exists + if (caseActivityType === CaseActivityType.REJECTED && !comment) { return false; } @@ -25,6 +25,6 @@ export class CaseActivityCommentConstraint } defaultMessage() { - return `Comment is required when activity type is ${CaseActivityType.APPROVED} or ${CaseActivityType.REJECTED} `; + return `Comment is required when activity type is ${CaseActivityType.REJECTED} `; } } diff --git a/vehicles/src/modules/case-management/case-management.service.ts b/vehicles/src/modules/case-management/case-management.service.ts index 12fb49d88..3a51b8009 100644 --- a/vehicles/src/modules/case-management/case-management.service.ts +++ b/vehicles/src/modules/case-management/case-management.service.ts @@ -23,6 +23,7 @@ import { CaseNotes } from './entities/case-notes.entity'; import { InjectMapper } from '@automapper/nestjs'; import { Mapper } from '@automapper/core'; import { ReadCaseEvenDto } from './dto/response/read-case-event.dto'; +import { ReadCaseActivityDto } from './dto/response/read-case-activity.dto'; @Injectable() export class CaseManagementService { @@ -32,6 +33,8 @@ export class CaseManagementService { private dataSource: DataSource, @InjectRepository(Case) private readonly caseRepository: Repository, + @InjectRepository(CaseActivity) + private readonly caseActivityRepository: Repository, ) {} /** @@ -126,9 +129,7 @@ export class CaseManagementService { existingCase && existingCase?.caseStatusType !== CaseStatusType.CLOSED ) { - throwUnprocessableEntityException( - 'An active case exists for the given application id', - ); + throwUnprocessableEntityException('Application in queue already.'); } let newCase = new Case(); newCase.caseType = CaseType.DEFAULT; @@ -218,9 +219,7 @@ export class CaseManagementService { if (!existingCase) { throw new DataNotFoundException(); } else if (existingCase.caseStatusType === CaseStatusType.CLOSED) { - throwUnprocessableEntityException( - 'Invalid status. Case is already closed', - ); + throwUnprocessableEntityException('Application no longer available.'); } existingCase.caseStatusType = CaseStatusType.CLOSED; //Rename to CaseStatusType @@ -306,9 +305,7 @@ export class CaseManagementService { if (!existingCase) { throw new DataNotFoundException(); } else if (existingCase.caseStatusType === CaseStatusType.CLOSED) { - throwUnprocessableEntityException( - 'Invalid status. Case is already closed', - ); + throwUnprocessableEntityException('Application no longer available.'); } existingCase.assignedUser = new User(); @@ -402,14 +399,8 @@ export class CaseManagementService { } if (!existingCase) { throw new DataNotFoundException(); - } else if (existingCase.caseStatusType === CaseStatusType.CLOSED) { - throwUnprocessableEntityException( - 'Invalid status. Case is already closed', - ); } else if (existingCase.caseStatusType !== CaseStatusType.OPEN) { - throwUnprocessableEntityException( - 'Cannot start workflow. Invalid status.', - ); + throwUnprocessableEntityException('Application no longer available.'); } existingCase.caseStatusType = CaseStatusType.IN_PROGRESS; @@ -502,14 +493,12 @@ export class CaseManagementService { } if (!existingCase) { throw new DataNotFoundException(); - } else if (existingCase.caseStatusType === CaseStatusType.CLOSED) { + } else if (existingCase.assignedUser?.userGUID !== currentUser.userGUID) { throwUnprocessableEntityException( - 'Invalid status. Case is already closed', + `Application no longer available. This application is claimed by ${existingCase.assignedUser?.userName}`, ); } else if (existingCase.caseStatusType !== CaseStatusType.IN_PROGRESS) { - throwUnprocessableEntityException( - 'Cannot complete workflow. Invalid status.', - ); + throwUnprocessableEntityException('Application no longer available.'); } let caseNotes: CaseNotes; @@ -534,6 +523,8 @@ export class CaseManagementService { newActivity.caseEvent = newEvent; newActivity.caseActivityType = caseActivityType; newActivity.dateTime = new Date(); + newActivity.user = new User(); + newActivity.user.userGUID = currentUser.userGUID; setBaseEntityProperties({ entity: newActivity, currentUser }); if (comment) { newActivity.caseNotes = caseNotes; @@ -616,13 +607,9 @@ export class CaseManagementService { } if (!existingCase) { throw new DataNotFoundException(); - } else if (existingCase.caseStatusType === CaseStatusType.CLOSED) { - throwUnprocessableEntityException( - 'Invalid status. Case is already closed', - ); - } else if (existingCase.caseStatusType === CaseStatusType.IN_PROGRESS) { + } else if (existingCase.caseStatusType !== CaseStatusType.OPEN) { throwUnprocessableEntityException( - 'Unable to withdraw the application in review', + 'Application Status Application(s) have either been withdrawn or are in review by the Provincial Permit Centre.', ); } @@ -638,6 +625,8 @@ export class CaseManagementService { newActivity.caseEvent = newEvent; newActivity.caseActivityType = CaseActivityType.WITHDRAWN; newActivity.dateTime = new Date(); + newActivity.user = new User(); + newActivity.user.userGUID = currentUser.userGUID; setBaseEntityProperties({ entity: newActivity, currentUser }); await queryRunner.manager.save(newActivity); @@ -720,13 +709,8 @@ export class CaseManagementService { } if (!existingCase) { throw new DataNotFoundException(); - } - if (existingCase.caseStatusType === CaseStatusType.CLOSED) { - throwUnprocessableEntityException( - 'Invalid status. Case is already closed', - ); } else if (existingCase.caseStatusType !== CaseStatusType.IN_PROGRESS) { - throwUnprocessableEntityException('Cannot add notes. Invalid status.'); + throwUnprocessableEntityException('Application no longer available.'); } try { let newEvent = this.createEvent( @@ -833,4 +817,51 @@ export class CaseManagementService { } } } + + /** + * Retrieves the activity history for a specific case by fetching and mapping `CaseActivity` records. + * Filters are applied based on the permit's `applicationId` and the specified `caseActivityType`. + * Joins additional details, including user information and associated case notes, for each activity. + * + * @param currentUser - The current user executing the action. + * @param applicationId - The ID of the permit associated with the case. + * @param caseActivityType - The type of case activity to filter. + * @returns A `Promise` containing the list of activities for the specified case. + */ + @LogAsyncMethodExecution() + async fetchActivityHistory({ + currentUser, + applicationId, + caseActivityType, + }: { + currentUser: IUserJWT; + applicationId: Nullable; + caseActivityType: CaseActivityType; + }): Promise { + const caseActivity = await this.caseActivityRepository + .createQueryBuilder('caseActivity') + .innerJoinAndSelect('caseActivity.user', 'user') + .leftJoinAndSelect('caseActivity.caseNotes', 'caseNotes') + .innerJoinAndSelect('caseActivity.case', 'case') + .innerJoinAndSelect('case.permit', 'permit') + .where('permit.id = :applicationId', { applicationId }) + .andWhere('caseActivity.caseActivityType = :caseActivityType', { + caseActivityType, + }) + .orderBy('caseActivity.dateTime', 'DESC') + .getMany(); + + const caseActivityDto = await this.classMapper.mapArrayAsync( + caseActivity, + CaseActivity, + ReadCaseActivityDto, + { + extraArgs: () => ({ + currentUser: currentUser, + }), + }, + ); + + return caseActivityDto; + } } diff --git a/vehicles/src/modules/case-management/dto/response/read-case-activity.dto.ts b/vehicles/src/modules/case-management/dto/response/read-case-activity.dto.ts new file mode 100644 index 000000000..becd1fb65 --- /dev/null +++ b/vehicles/src/modules/case-management/dto/response/read-case-activity.dto.ts @@ -0,0 +1,37 @@ +import { AutoMap } from '@automapper/classes'; +import { ApiProperty } from '@nestjs/swagger'; +import { Nullable } from '../../../../common/types/common'; + +export class ReadCaseActivityDto { + @AutoMap() + @ApiProperty({ + description: 'The unique case acitivty id.', + example: 1, + }) + caseActivityId: number; + + @AutoMap() + @ApiProperty({ + description: + 'The user name or id linked to the activity. This value is returned only when queried by a staff user.', + example: 'JSMITH', + required: false, + }) + userName?: string; + + @AutoMap() + @ApiProperty({ + description: 'The date and time when the activity took place.', + example: '2023-10-11T23:26:51.170Z', + }) + dateTime: string; + + @AutoMap() + @ApiProperty({ + description: 'The reason for activity.', + example: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', + required: false, + type: 'string', + }) + caseNotes?: Nullable; +} diff --git a/vehicles/src/modules/case-management/profiles/case-management.profile.ts b/vehicles/src/modules/case-management/profiles/case-management.profile.ts index 380c9f0ff..465818f12 100644 --- a/vehicles/src/modules/case-management/profiles/case-management.profile.ts +++ b/vehicles/src/modules/case-management/profiles/case-management.profile.ts @@ -1,8 +1,19 @@ import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; -import { Mapper, createMap } from '@automapper/core'; +import { + Mapper, + createMap, + forMember, + mapFrom, + mapWithArguments, +} from '@automapper/core'; import { Injectable } from '@nestjs/common'; import { CaseEvent } from '../entities/case-event.entity'; import { ReadCaseEvenDto } from '../dto/response/read-case-event.dto'; +import { CaseActivity } from '../entities/case-activity.entity'; +import { ReadCaseActivityDto } from '../dto/response/read-case-activity.dto'; +import { IUserJWT } from '../../../common/interface/user-jwt.interface'; +import { doesUserHaveRole } from '../../../common/helper/auth.helper'; +import { IDIR_USER_ROLE_LIST } from '../../../common/enum/user-role.enum'; @Injectable() export class CaseManagementProfile extends AutomapperProfile { @@ -13,6 +24,28 @@ export class CaseManagementProfile extends AutomapperProfile { override get profile() { return (mapper: Mapper) => { createMap(mapper, CaseEvent, ReadCaseEvenDto); + + createMap( + mapper, + CaseActivity, + ReadCaseActivityDto, + forMember( + (d) => d.caseNotes, + mapFrom((s) => s?.caseNotes?.comment), + ), + forMember( + (d) => d.userName, + mapWithArguments( + (source, { currentUser }: { currentUser: IUserJWT }) => { + if ( + doesUserHaveRole(currentUser.orbcUserRole, IDIR_USER_ROLE_LIST) + ) { + return source.user?.userName; + } + }, + ), + ), + ); }; } } diff --git a/vehicles/src/modules/permit-application-payment/application/application.service.ts b/vehicles/src/modules/permit-application-payment/application/application.service.ts index a21a8305e..4b5386a0e 100644 --- a/vehicles/src/modules/permit-application-payment/application/application.service.ts +++ b/vehicles/src/modules/permit-application-payment/application/application.service.ts @@ -72,6 +72,7 @@ import { PermitData } from '../../../common/interface/permit.template.interface' import { ApplicationApprovedNotification } from '../../../common/interface/application-approved.notification.interface'; import { ApplicationRejectedNotification } from '../../../common/interface/application-rejected.notification.interface'; import { convertUtcToPt } from '../../../common/helper/date-time.helper'; +import { ReadCaseActivityDto } from '../../case-management/dto/response/read-case-activity.dto'; @Injectable() export class ApplicationService { @@ -270,6 +271,16 @@ export class ApplicationService { companyId?: number, ): Promise { const application = await this.findOne(applicationId, companyId); + let readCaseActivityList: ReadCaseActivityDto[]; + if (isPermitTypeEligibleForQueue(application?.permitType)) { + readCaseActivityList = + await this.caseManagementService.fetchActivityHistory({ + applicationId, + currentUser, + caseActivityType: CaseActivityType.REJECTED, + }); + } + const readPermitApplicationdto = await this.classMapper.mapAsync( application, Permit, @@ -277,6 +288,7 @@ export class ApplicationService { { extraArgs: () => ({ currentUserRole: currentUser?.orbcUserRole, + readCaseActivityList: readCaseActivityList, }), }, ); diff --git a/vehicles/src/modules/permit-application-payment/application/dto/response/read-application.dto.ts b/vehicles/src/modules/permit-application-payment/application/dto/response/read-application.dto.ts index 704e11fa5..eb0bf471a 100644 --- a/vehicles/src/modules/permit-application-payment/application/dto/response/read-application.dto.ts +++ b/vehicles/src/modules/permit-application-payment/application/dto/response/read-application.dto.ts @@ -4,6 +4,7 @@ import { ApplicationStatus } from 'src/common/enum/application-status.enum'; import { PermitApplicationOrigin } from 'src/common/enum/permit-application-origin.enum'; import { PermitApprovalSource } from 'src/common/enum/permit-approval-source.enum'; import { PermitType } from 'src/common/enum/permit-type.enum'; +import { ReadCaseActivityDto } from '../../../../case-management/dto/response/read-case-activity.dto'; export class ReadApplicationDto { @AutoMap() @@ -128,4 +129,12 @@ export class ReadApplicationDto { required: false, }) applicant: string; + + @AutoMap() + @ApiProperty({ + description: 'Application rejection history', + type: [ReadCaseActivityDto], + required: false, + }) + rejectionHistory?: ReadCaseActivityDto[]; } diff --git a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts index 48226dcd9..d85a45442 100644 --- a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts +++ b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts @@ -26,6 +26,7 @@ import { ApplicationQueueStatus, CaseStatusType, } from '../../../../common/enum/case-status-type.enum'; +import { ReadCaseActivityDto } from '../../../case-management/dto/response/read-case-activity.dto'; @Injectable() export class ApplicationProfile extends AutomapperProfile { @@ -207,6 +208,21 @@ export class ApplicationProfile extends AutomapperProfile { } }), ), + forMember( + (d) => d.rejectionHistory, + mapWithArguments( + ( + s, + { + readCaseActivityList, + }: { readCaseActivityList: ReadCaseActivityDto[] }, + ) => { + if (readCaseActivityList?.length) { + return readCaseActivityList; + } + }, + ), + ), ); createMap( From d60f72083732b57ddf227ea3a507fef9723a3b1b Mon Sep 17 00:00:00 2001 From: John Fletcher <113134542+john-fletcher-aot@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:14:36 -0700 Subject: [PATCH 02/58] feat: Add sample policy configuration JSON, and add documentation (#1591) Co-authored-by: Praveen Raju <80779423+praju-aot@users.noreply.github.com> --- .../dbo.ORBC_POLICY_CONFIGURATION.Table.sql | 6 ++ .../scripts/utility/refresh-sample-data.sh | 2 + .../docs/validation-result-reference.md | 89 +++++++++++++++++++ .../src/_examples/validate-invalid-tros.ts | 22 +++++ 4 files changed, 119 insertions(+) create mode 100644 database/mssql/scripts/sampledata/dbo.ORBC_POLICY_CONFIGURATION.Table.sql create mode 100644 policy-engine/docs/validation-result-reference.md create mode 100644 policy-engine/src/_examples/validate-invalid-tros.ts diff --git a/database/mssql/scripts/sampledata/dbo.ORBC_POLICY_CONFIGURATION.Table.sql b/database/mssql/scripts/sampledata/dbo.ORBC_POLICY_CONFIGURATION.Table.sql new file mode 100644 index 000000000..0c4c5bff1 --- /dev/null +++ b/database/mssql/scripts/sampledata/dbo.ORBC_POLICY_CONFIGURATION.Table.sql @@ -0,0 +1,6 @@ +SET NOCOUNT ON +GO +INSERT [dbo].[ORBC_POLICY_CONFIGURATION] (EFFECTIVE_DATE, IS_DRAFT, CHANGE_DESCRIPTION, POLICY_JSON) +VALUES (GETUTCDATE(), 'N', N'In-progress sample policy configuration', +N'{"version":"2024.03.18.001","geographicRegions":[{"id":"LMN","name":"Lower Mainland"},{"id":"KTN","name":"Kootenay"},{"id":"PCE","name":"Peace"}],"rangeMatrices":[{"id":"annualFeeCV","name":"Annual licensing fee for commercial vehicle","matrix":[{"min":0,"max":500,"value":42},{"min":501,"max":1000,"value":49},{"min":1001,"max":1500,"value":60},{"min":1501,"max":2000,"value":74},{"min":2001,"max":2500,"value":85},{"min":2501,"max":3000,"value":97},{"min":3001,"max":3500,"value":108},{"min":3501,"max":4000,"value":127},{"min":4001,"max":4500,"value":147},{"min":4501,"max":5000,"value":173},{"min":5001,"max":5500,"value":193},{"min":5501,"max":6000,"value":213},{"min":6001,"max":6500,"value":230},{"min":6501,"max":7000,"value":250},{"min":7001,"max":7500,"value":266},{"min":7501,"max":8000,"value":292},{"min":8001,"max":8500,"value":320},{"min":8501,"max":9000,"value":347},{"min":9001,"max":9500,"value":376},{"min":9501,"max":10000,"value":395},{"min":10001,"max":10500,"value":416},{"min":10501,"max":11000,"value":437},{"min":11001,"max":11500,"value":450},{"min":11501,"max":12000,"value":469},{"min":12001,"max":12500,"value":488},{"min":12501,"max":13000,"value":502},{"min":13001,"max":13500,"value":526},{"min":13501,"max":14000,"value":553},{"min":14001,"max":14500,"value":580},{"min":14501,"max":15000,"value":607},{"min":15001,"max":15500,"value":638},{"min":15501,"max":16000,"value":680},{"min":16001,"max":16500,"value":721},{"min":16501,"max":17000,"value":761},{"min":17001,"max":17500,"value":806},{"min":17501,"max":18000,"value":837},{"min":18001,"max":18500,"value":861},{"min":18501,"max":19000,"value":890},{"min":19001,"max":19500,"value":917},{"min":19501,"max":20000,"value":944},{"min":20001,"max":20500,"value":977},{"min":20501,"max":21000,"value":1003},{"min":21001,"max":21500,"value":1030},{"min":21501,"max":22000,"value":1057},{"min":22001,"max":22500,"value":1084},{"min":22501,"max":23000,"value":1111},{"min":23001,"max":23500,"value":1140},{"min":23501,"max":24000,"value":1170},{"min":24001,"max":24500,"value":1199},{"min":24501,"max":25000,"value":1239},{"min":25001,"max":25500,"value":1285},{"min":25501,"max":26000,"value":1326},{"min":26001,"max":26500,"value":1367},{"min":26501,"max":27000,"value":1395},{"min":27001,"max":27500,"value":1424},{"min":27501,"max":28000,"value":1450},{"min":28001,"max":28500,"value":1479},{"min":28501,"max":29000,"value":1505},{"min":29001,"max":29500,"value":1534},{"min":29501,"max":30000,"value":1565},{"min":30001,"max":31000,"value":1591},{"min":31001,"max":32000,"value":1644},{"min":32001,"max":33000,"value":1696},{"min":33001,"max":34000,"value":1751},{"min":34001,"max":35000,"value":1805},{"min":35001,"max":36000,"value":1890},{"min":36001,"max":37000,"value":2018},{"min":37001,"max":38000,"value":2088},{"min":38001,"max":39000,"value":2159},{"min":39001,"max":40000,"value":2229},{"min":40001,"max":41000,"value":2300},{"min":41001,"max":42000,"value":2373},{"min":42001,"max":43000,"value":2445},{"min":43001,"max":44000,"value":2514},{"min":44001,"max":45000,"value":2585},{"min":45001,"max":46000,"value":2690},{"min":46001,"max":47000,"value":2799},{"min":47001,"max":48000,"value":2871},{"min":48001,"max":49000,"value":2940},{"min":49001,"max":50000,"value":3012},{"min":50001,"max":51000,"value":3061},{"min":51001,"max":52000,"value":3127},{"min":52001,"max":53000,"value":3192},{"min":53001,"max":54000,"value":3257},{"min":54001,"max":55000,"value":3322},{"min":55001,"max":56000,"value":3387},{"min":56001,"max":57000,"value":3452},{"min":57001,"max":58000,"value":3516},{"min":58001,"max":59000,"value":3581},{"min":59001,"max":60000,"value":3647},{"min":60001,"max":61000,"value":3710},{"min":61001,"max":62000,"value":3775},{"min":62001,"max":63000,"value":3840},{"min":63001,"max":63500,"value":3905}]},{"id":"annualFeePassenger","name":"Annual licensing fee for commercial passenger vehicle","matrix":[{"min":0,"max":500,"value":40},{"min":501,"max":1000,"value":47},{"min":1001,"max":1500,"value":57},{"min":1501,"max":2000,"value":70},{"min":2001,"max":2500,"value":81},{"min":2501,"max":3000,"value":92},{"min":3001,"max":3500,"value":103},{"min":3501,"max":4000,"value":121},{"min":4001,"max":4500,"value":140},{"min":4501,"max":5000,"value":165},{"min":5001,"max":5500,"value":184},{"min":5501,"max":6000,"value":203},{"min":6001,"max":6500,"value":219},{"min":6501,"max":7000,"value":238},{"min":7001,"max":7500,"value":253},{"min":7501,"max":8000,"value":278},{"min":8001,"max":8500,"value":305},{"min":8501,"max":9000,"value":330},{"min":9001,"max":9500,"value":358},{"min":9501,"max":10000,"value":376},{"min":10001,"max":10500,"value":396},{"min":10501,"max":11000,"value":416},{"min":11001,"max":11500,"value":429},{"min":11501,"max":12000,"value":447},{"min":12001,"max":12500,"value":465},{"min":12501,"max":13000,"value":478},{"min":13001,"max":13500,"value":501},{"min":13501,"max":14000,"value":527},{"min":14001,"max":14500,"value":552},{"min":14501,"max":15000,"value":578},{"min":15001,"max":15500,"value":608},{"min":15501,"max":16000,"value":648},{"min":16001,"max":16500,"value":687},{"min":16501,"max":17000,"value":725},{"min":17001,"max":17500,"value":768},{"min":17501,"max":18000,"value":797},{"min":18001,"max":18500,"value":820},{"min":18501,"max":19000,"value":848},{"min":19001,"max":19500,"value":873},{"min":19501,"max":20000,"value":899},{"min":20001,"max":20500,"value":930},{"min":20501,"max":21000,"value":955},{"min":21001,"max":21500,"value":981},{"min":21501,"max":22000,"value":1007},{"min":22001,"max":22500,"value":1032},{"min":22501,"max":23000,"value":1058},{"min":23001,"max":23500,"value":1086},{"min":23501,"max":24000,"value":1114},{"min":24001,"max":24500,"value":1142},{"min":24501,"max":25000,"value":1180},{"min":25001,"max":25500,"value":1224},{"min":25501,"max":26000,"value":1263},{"min":26001,"max":26500,"value":1302},{"min":26501,"max":27000,"value":1329},{"min":27001,"max":27500,"value":1356},{"min":27501,"max":28000,"value":1381},{"min":28001,"max":28500,"value":1409},{"min":28501,"max":29000,"value":1433},{"min":29001,"max":29500,"value":1461},{"min":29501,"max":30000,"value":1490},{"min":30001,"max":31000,"value":1516},{"min":31001,"max":32000,"value":1569},{"min":32001,"max":33000,"value":1621},{"min":33001,"max":34000,"value":1676},{"min":34001,"max":35000,"value":1730},{"min":35001,"max":36000,"value":1815},{"min":36001,"max":37000,"value":1943},{"min":37001,"max":38000,"value":2013},{"min":38001,"max":39000,"value":2084},{"min":39001,"max":40000,"value":2154},{"min":40001,"max":41000,"value":2225},{"min":41001,"max":42000,"value":2298},{"min":42001,"max":43000,"value":2370},{"min":43001,"max":44000,"value":2439},{"min":44001,"max":45000,"value":2510},{"min":45001,"max":46000,"value":2615},{"min":46001,"max":47000,"value":2724},{"min":47001,"max":48000,"value":2796},{"min":48001,"max":49000,"value":2865},{"min":49001,"max":50000,"value":2937},{"min":50001,"max":51000,"value":2986},{"min":51001,"max":52000,"value":3052},{"min":52001,"max":53000,"value":3117},{"min":53001,"max":54000,"value":3182},{"min":54001,"max":55000,"value":3247},{"min":55001,"max":56000,"value":3312},{"min":56001,"max":57000,"value":3377},{"min":57001,"max":58000,"value":3441},{"min":58001,"max":59000,"value":3506},{"min":59001,"max":60000,"value":3572},{"min":60001,"max":61000,"value":3635},{"min":61001,"max":62000,"value":3700},{"min":62001,"max":63000,"value":3765},{"min":63001,"max":63500,"value":3830}]},{"id":"annualFeeIndustrial","name":"Annual licensing fee for an industrial machine","matrix":[{"min":0,"max":2000,"value":45},{"min":2001,"max":5000,"value":69},{"min":5001,"max":7000,"value":110},{"min":7001,"max":9000,"value":164},{"min":9001,"max":11000,"value":216},{"min":11001,"value":260}]},{"id":"annualFeeFarm","name":"Annual licensing fee for farm vehicle","matrix":[{"min":0,"max":500,"value":30},{"min":501,"max":1000,"value":40},{"min":1001,"max":1500,"value":47},{"min":1501,"max":2000,"value":55},{"min":2001,"max":2500,"value":77},{"min":2501,"max":3000,"value":101},{"min":3001,"max":3500,"value":142},{"min":3501,"max":4000,"value":181},{"min":4001,"max":4500,"value":207},{"min":4501,"max":5000,"value":243},{"min":5001,"max":5500,"value":278},{"min":5501,"max":6000,"value":322},{"min":6001,"max":6500,"value":355},{"min":6501,"max":7000,"value":396},{"min":7001,"max":7500,"value":427},{"min":7501,"max":8000,"value":473},{"min":8001,"max":8500,"value":524},{"min":8501,"max":9000,"value":558},{"min":9001,"max":9500,"value":596},{"min":9501,"max":10000,"value":633},{"min":10001,"max":10500,"value":669},{"min":10501,"max":11000,"value":711},{"min":11001,"max":11500,"value":744},{"min":11501,"max":12000,"value":784},{"min":12001,"max":12500,"value":824},{"min":12501,"max":13000,"value":863},{"min":13001,"max":13500,"value":883},{"min":13501,"max":14000,"value":899},{"min":14001,"max":14500,"value":919},{"min":14501,"max":15000,"value":940},{"min":15001,"max":15500,"value":960},{"min":15501,"max":16000,"value":979},{"min":16001,"max":16500,"value":997},{"min":16501,"max":17000,"value":1017},{"min":17001,"max":17500,"value":1036},{"min":17501,"max":18000,"value":1056},{"min":18001,"max":18500,"value":1076},{"min":18501,"max":19000,"value":1096},{"min":19001,"max":19500,"value":1114},{"min":19501,"max":20000,"value":1134},{"min":20001,"max":20500,"value":1154},{"min":20501,"max":21000,"value":1174},{"min":21001,"max":21500,"value":1192},{"min":21501,"max":22000,"value":1211},{"min":22001,"max":22500,"value":1231},{"min":22501,"max":23000,"value":1251},{"min":23001,"max":23500,"value":1270},{"min":23501,"max":24000,"value":1289},{"min":24001,"max":24400,"value":1309}]}],"commonRules":[{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.companyName"}},"event":{"type":"violation","params":{"message":"Company is required","code":"field-validation-error","fieldReference":"permitData.companyName"}}},{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.contactDetails.firstName"}},"event":{"type":"violation","params":{"message":"Contact first name is required","code":"field-validation-error","fieldReference":"permitData.contactDetails.firstName"}}},{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.contactDetails.lastName"}},"event":{"type":"violation","params":{"message":"Contact last name is required","code":"field-validation-error","fieldReference":"permitData.contactDetails.lastName"}}},{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.contactDetails.phone1"}},"event":{"type":"violation","params":{"message":"Contact phone number is required","code":"field-validation-error","fieldReference":"permitData.contactDetails.phone1"}}},{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.contactDetails.email"}},"event":{"type":"violation","params":{"message":"Company contact email is required","code":"field-validation-error","fieldReference":"permitData.contactDetails.email"}}},{"conditions":{"any":[{"fact":"permitData","operator":"dateLessThan","value":{"fact":"validationDate"},"path":"$.startDate"}]},"event":{"type":"violation","params":{"message":"Permit start date cannot be in the past","code":"field-validation-error","fieldReference":"permitData.startDate"}}},{"conditions":{"not":{"fact":"permitData","operator":"regex","value":"^[a-zA-Z0-9]{6}$","path":"$.vehicleDetails.vin"}},"event":{"type":"violation","params":{"message":"Vehicle Identification Number (vin) must be 6 alphanumeric characters","code":"field-validation-error","fieldReference":"permitData.vehicleDetails.vin"}}},{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.vehicleDetails.plate"}},"event":{"type":"violation","params":{"message":"Vehicle plate is required","code":"field-validation-error","fieldReference":"permitData.vehicleDetails.plate"}}},{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.vehicleDetails.make"}},"event":{"type":"violation","params":{"message":"Vehicle make is required","code":"field-validation-error","fieldReference":"permitData.vehicleDetails.make"}}},{"conditions":{"not":{"fact":"permitData","operator":"greaterThan","value":1900,"path":"$.vehicleDetails.year"}},"event":{"type":"violation","params":{"message":"Vehicle year is required","code":"field-validation-error","fieldReference":"permitData.vehicleDetails.year"}}},{"conditions":{"not":{"fact":"permitData","operator":"stringMinimumLength","value":1,"path":"$.vehicleDetails.countryCode"}},"event":{"type":"violation","params":{"message":"Vehicle country of registration is required","code":"field-validation-error","fieldReference":"permitData.vehicleDetails.countryCode"}}}],"permitTypes":[{"id":"TROS","name":"Term Oversize","routingRequired":false,"weightDimensionRequired":false,"sizeDimensionRequired":false,"commodityRequired":false,"allowedVehicles":["BOOSTER","DOLLIES","EXPANDO","FEBGHSE","FECVYER","FEDRMMX","FEPNYTR","FESEMTR","FEWHELR","FLOATTR","FULLLTL","HIBOEXP","HIBOFLT","JEEPSRG","LOGDGLG","LOGFULL","LOGNTAC","LOGOWBK","LOGSMEM","LOGTNDM","LOGTRIX","ODTRLEX","OGOSFDT","PLATFRM","POLETRL","PONYTRL","REDIMIX","SEMITRL","STBTRAN","STCHIPS","STCRANE","STINGAT","STLOGNG","STNTSHC","STREEFR","STSDBDK","STSTNGR","STWHELR","STWIDWH","BUSTRLR","CONCRET","DDCKBUS","GRADERS","LOGGING","LOGOFFH","LWBTRCT","OGBEDTK","OGOILSW","PICKRTT","PLOWBLD","REGTRCK","STINGER","TOWVEHC","TRKTRAC"],"rules":[{"conditions":{"all":[{"not":{"fact":"permitData","operator":"in","value":[30,60,90,120,150,180,210,240,270,300,330],"path":"$.permitDuration"}},{"not":{"fact":"permitData","operator":"equal","value":{"fact":"daysInPermitYear"},"path":"$.permitDuration"}}]},"event":{"type":"violation","params":{"message":"Duration must be in 30 day increments or a full year","code":"field-validation-error","fieldReference":"permitData.permitDuration"}}},{"conditions":{"not":{"fact":"permitData","path":"$.vehicleDetails.vehicleSubType","operator":"in","value":{"fact":"allowedVehicles"}}},"event":{"type":"violation","params":{"message":"Vehicle type not permittable for this permit type","code":"field-validation-error","fieldReference":"permitData.vehicleDetails.vehicleSubType"}}}],"costRules":[{"fact":"costPerMonth","params":{"cost":30}}]},{"id":"TROW","name":"Term Overweight","routingRequired":false,"weightDimensionRequired":false,"sizeDimensionRequired":false,"commodityRequired":false,"allowedVehicles":["DOLLIES","FEBGHSE","FECVYER","FEDRMMX","FEPNYTR","FESEMTR","FEWHELR","REDIMIX","CONCRET","CRAFTAT","CRAFTMB","GRADERS","MUNFITR","OGOILSW","OGSERVC","OGSRRAH","PICKRTT","TOWVEHC"],"rules":[{"conditions":{"all":[{"not":{"fact":"permitData","path":"$.permitDuration","operator":"in","value":[30,60,90,120,150,180,210,240,270,300,330]}},{"not":{"fact":"permitData","path":"$.permitDuration","operator":"equal","value":{"fact":"daysInPermitYear"}}}]},"event":{"type":"violation","params":{"message":"Duration must be in 30 day increments or a full year","code":"field-validation-error","fieldReference":"permitData.permitDuration"}}},{"conditions":{"not":{"fact":"permitData","path":"$.vehicleDetails.vehicleSubType","operator":"in","value":{"fact":"allowedVehicles"}}},"event":{"type":"violation","params":{"message":"Vehicle type not permittable for this permit type","code":"field-validation-error","fieldReference":"permitData.vehicleDetails.vehicleSubType"}}}],"costRules":[{"fact":"costPerMonth","params":{"cost":100}}]},{"id":"STOS","name":"Single Trip Oversize","routingRequired":true,"weightDimensionRequired":false,"sizeDimensionRequired":true,"commodityRequired":true,"allowedCommodities":["EMPTYXX","BRGBEAM","AUTOCRR","BRSHCUT"],"allowedVehicles":["DOLLIES","FEBGHSE","FECVYER","FEDRMMX","FEPNYTR","FESEMTR","FEWHELR","REDIMIX","CONCRET","CRAFTAT","CRAFTMB","GRADERS","MUNFITR","OGOILSW","OGSERVC","OGSRRAH","PICKRTT","TOWVEHC"],"rules":[{"conditions":{"any":[{"not":{"fact":"permitData","operator":"lessThanInclusive","value":7,"path":"$.permitDuration"}},{"not":{"fact":"permitData","operator":"greaterThan","value":0,"path":"$.permitDuration"}}]},"event":{"type":"violation","params":{"message":"Duration must be 7 days or less","code":"field-validation-error","fieldReference":"permitData.permitDuration"}}},{"conditions":{"not":{"fact":"configurationIsValid","operator":"equal","value":true}},"event":{"type":"violation","params":{"message":"Vehicle configuration is not permittable for this commodity","code":"field-validation-error","fieldReference":"permitData.vehicleConfiguration.trailers"}}}],"costRules":[{"fact":"fixedCost","params":{"cost":15}}]}],"globalWeightDefaults":{"powerUnits":[],"trailers":[]},"vehicleCategories":{"trailerCategories":[{"id":"trailer","name":"Default trailer category"},{"id":"accessory","name":"Accessory trailer such as jeep or booster, to be used alongside other trailers. Not permittable on its own as a trailer in a combination."},{"id":"pseudo","name":"Placeholder for a trailer in a combination with no trailer (such as when a brushcutter is permitted with no trailer)."}],"powerUnitCategories":[{"id":"powerunit","name":"Default power unit category"}]},"vehicleTypes":{"powerUnitTypes":[{"id":"BUSCRUM","name":"Buses/Crummies","category":"powerunit"},{"id":"BUSTRLR","name":"Inter-City Bus (Pulling Pony Trailer)","category":"powerunit"},{"id":"CONCRET","name":"Concrete Pumper Trucks","category":"powerunit"},{"id":"CRAFTAT","name":"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain","category":"powerunit"},{"id":"CRAFTMB","name":"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile","category":"powerunit"},{"id":"DDCKBUS","name":"Double Decker Buses","category":"powerunit"},{"id":"FARMVEH","name":"Farm Vehicles","category":"powerunit"},{"id":"GRADERS","name":"Fixed Equipment - Trucks/Graders etc.","category":"powerunit"},{"id":"LCVRMDB","name":"Long Combination Vehicles (LCV) - Rocky Mountain Doubles","category":"powerunit"},{"id":"LCVTPDB","name":"Long Combination Vehicles (LCV) - Turnpike Doubles","category":"powerunit"},{"id":"LOGGING","name":"Logging Trucks","category":"powerunit"},{"id":"LOGOFFH","name":"Logging Trucks - Off-Highway","category":"powerunit"},{"id":"LWBTRCT","name":"Long Wheelbase Truck Tractors Exceeding 6.2 m up to 7.25 m","category":"powerunit"},{"id":"MUNFITR","name":"Municipal Fire Trucks","category":"powerunit"},{"id":"OGBEDTK","name":"Oil and Gas - Bed Trucks","category":"powerunit"},{"id":"OGOILSW","name":"Oil and Gas - Oilfield Sows","category":"powerunit"},{"id":"OGSERVC","name":"Oil and Gas - Service Rigs","category":"powerunit"},{"id":"OGSRRAH","name":"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)","category":"powerunit"},{"id":"PICKRTT","name":"Picker Truck Tractors","category":"powerunit"},{"id":"PLOWBLD","name":"Trucks Equipped with Front or Underbody Plow Blades","category":"powerunit"},{"id":"PUTAXIS","name":"Taxis","category":"powerunit"},{"id":"REGTRCK","name":"Trucks","category":"powerunit"},{"id":"SCRAPER","name":"Scrapers","category":"powerunit"},{"id":"SPAUTHV","name":"Specially Authorized Vehicles","category":"powerunit"},{"id":"STINGER","name":"Truck Tractors - Stinger Steered","category":"powerunit"},{"id":"TOWVEHC","name":"Tow Vehicles","category":"powerunit"},{"id":"TRKTRAC","name":"Truck Tractors","category":"powerunit"}],"trailerTypes":[{"id":"BOOSTER","name":"Boosters","category":"accessory"},{"id":"DBTRBTR","name":"Tandem/Tridem Drive B-Train (Super B-Train)","category":"trailer"},{"id":"DOLLIES","name":"Dollies","category":"trailer"},{"id":"EXPANDO","name":"Expando Semi-Trailers","category":"trailer"},{"id":"FEBGHSE","name":"Fixed Equipment - Portable Asphalt Baghouses","category":"trailer"},{"id":"FECVYER","name":"Fixed Equipment - Conveyors (Semi-Trailers)","category":"trailer"},{"id":"FECVYPT","name":"Fixed Equipment - Conveyors (Pony Trailers)","category":"trailer"},{"id":"FEDRMMX","name":"Fixed Equipment - Counter Flow Asphalt Drum Mixers","category":"trailer"},{"id":"FEPNYTR","name":"Fixed Equipment - Pony Trailers","category":"trailer"},{"id":"FESEMTR","name":"Fixed Equipment - Semi-Trailers","category":"trailer"},{"id":"FEWHELR","name":"Fixed Equipment - Wheeler Semi-Trailers","category":"wheeler"},{"id":"FLOATTR","name":"Float Trailers","category":"wheeler"},{"id":"FULLLTL","name":"Full Trailers","category":"trailer"},{"id":"HIBOEXP","name":"Semi-Trailers - Hiboys/Expandos","category":"trailer"},{"id":"HIBOFLT","name":"Semi-Trailers - Hiboys/Flat Decks","category":"trailer"},{"id":"JEEPSRG","name":"Jeeps","category":"accessory"},{"id":"LOGDGLG","name":"Legacy Logging Trailer Combinations - Tandem Pole Trailers, Dogloggers","category":"trailer"},{"id":"LOGLGCY","name":"Legacy Logging Trailer Combinations","category":"trailer"},{"id":"LOGFULL","name":"Logging Trailer - Full Trailers, Tri Axle, Quad Axle","category":"trailer"},{"id":"LOGNTAC","name":"Legacy Logging Trailer Combinations - Non-TAC B-Trains","category":"trailer"},{"id":"LOGOWBK","name":"Logging Trailer - Overwidth Bunks","category":"trailer"},{"id":"LOGSMEM","name":"Logging Semi-Trailer - Empty, 3.2 m Bunks","category":"trailer"},{"id":"LOGTNDM","name":"Legacy Logging Trailer Combinations - Single Axle Jeeps, Tandem Axle Pole Trailers, Dogloggers","category":"trailer"},{"id":"LOGTRIX","name":"Legacy Logging Trailer Combinations - Single Axle Jeeps, Tri Axle Trailers","category":"trailer"},{"id":"MHMBSHG","name":"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW) with Attached Axles","category":"trailer"},{"id":"MHMBSHL","name":"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW) with Attached Axles","category":"trailer"},{"id":"ODTRLEX","name":"Overdimensional Trailers and Semi-Trailers (For Export)","category":"trailer"},{"id":"OGOSFDT","name":"Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers","category":"trailer"},{"id":"PLATFRM","name":"Platform Trailers","category":"trailer"},{"id":"PMHWAAX","name":"Park Model Homes with Attached Axles","category":"trailer"},{"id":"POLETRL","name":"Pole Trailers","category":"trailer"},{"id":"PONYTRL","name":"Pony Trailers","category":"trailer"},{"id":"REDIMIX","name":"Ready Mix Concrete Pump Semi-Trailers","category":"trailer"},{"id":"SEMITRL","name":"Semi-Trailers","category":"trailer"},{"id":"STACTRN","name":"Semi-Trailers - A-Trains and C-Trains","category":"trailer"},{"id":"STBTRAN","name":"Semi-Trailers - B-Trains","category":"trailer"},{"id":"STCHIPS","name":"Semi-Trailers - Walled B-Trains (Chip Trucks)","category":"trailer"},{"id":"STCRANE","name":"Semi-Trailers with Crane","category":"trailer"},{"id":"STINGAT","name":"Stinger Steered Automobile Transporters","category":"trailer"},{"id":"STLOGNG","name":"Semi-Trailers - Logging","category":"trailer"},{"id":"STNTSHC","name":"Semi-Trailers - Non-Tac Short Chassis","category":"trailer"},{"id":"STREEFR","name":"Semi-Trailers - Insulated Vans with Reefer/Refrigeration Units","category":"trailer"},{"id":"STROPRT","name":"Steering Trailers - Manned","category":"trailer"},{"id":"STRSELF","name":"Steering Trailers - Self/Remote","category":"trailer"},{"id":"STSDBDK","name":"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.","category":"trailer"},{"id":"STSTEER","name":"Semi-Trailers - Steering Trailers","category":"trailer"},{"id":"STSTNGR","name":"Semi-Trailers - Stinger Steered Automobile Transporters","category":"trailer"},{"id":"STWDTAN","name":"Semi-Trailers - Spread Tandems","category":"trailer"},{"id":"STWHELR","name":"Semi-Trailers - Wheelers","category":"trailer"},{"id":"STWIDWH","name":"Semi-Trailers - Wide Wheelers","category":"trailer"},{"id":"NONEXXX","name":"None","category":"pseudo"}]},"commodities":[{"id":"NONEXXX","name":"None","size":{"powerUnits":[{"type":"CONCRET","trailers":[{"type":"NONEXXX","jeep":false,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"l":15.5}]}]},{"type":"CRAFTAT","trailers":[{"type":"DOLLIES","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":3.8,"h":4.3,"l":25}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":3.8,"h":4.3,"l":14,"regions":[{"region":"PCE","l":15}]}]}]},{"type":"CRAFTMB","trailers":[{"type":"DOLLIES","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":3.8,"h":4.3,"l":25}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":3.8,"h":4.3,"l":14,"regions":[{"region":"PCE","l":15}]}]}]},{"type":"DDCKBUS","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"h":4.42,"regions":[{"region":"LMN","h":4.3},{"region":"KTN","h":4.3},{"region":"PCE","h":4.3}]}]}]},{"type":"GRADERS","trailers":[{"type":"FEPNYTR","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.2,"h":4.3,"l":31}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"w":3.5,"h":4.4,"l":12.5,"regions":[{"region":"LMN","h":4.3},{"region":"KTN","h":4.3},{"region":"PCE","h":5.33}]}]}]},{"type":"BUSTRLR","trailers":[{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{}]}]},{"type":"LOGOFFH","trailers":[{"type":"STLOGNG","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":4.4}]}]},{"type":"LCVRMDB","trailers":[{"type":"SEMITRL","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"l":32,"regions":[{"region":"PCE","l":31}]}]}]},{"type":"LCVTPDB","trailers":[{"type":"SEMITRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"l":41}]}]},{"type":"LWBTRCT","trailers":[{"type":"SEMITRL","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":2.6,"h":4.15,"l":23}]}]},{"type":"PICKRTT","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":2.6,"h":4.15,"l":16}]},{"type":"STCRANE","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":2.6,"h":4.15,"l":25}]},{"type":"STROPRT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":40,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STRSELF","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":36,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"SCRAPER","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"l":12.5}]}]},{"type":"TRKTRAC","trailers":[{"type":"FECVYER","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":4,"rp":9.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FEDRMMX","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FEBGHSE","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.5,"w":4.26,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FESEMTR","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FEWHELR","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"ODTRLEX","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"w":3.65}]},{"type":"REDIMIX","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"h":4.3,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STREEFR","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"h":4.3}]},{"type":"STNTSHC","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","l":32}]}]},{"type":"STROPRT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"h":4.15,"l":40}]},{"type":"STRSELF","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"l":36}]}]},{"type":"REGTRCK","trailers":[{"type":"FECVYPT","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":4,"rp":9.5,"w":3.2,"h":4.3,"l":31}]},{"type":"FEPNYTR","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.2,"h":4.3,"l":31}]},{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":6.5,"w":3.8,"h":4.3,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"MHMBSHL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"MHMBSHG","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":3.2,"h":4.3,"l":16,"regions":[{"region":"PCE","h":4.4}]}]},{"type":"ODTRLEX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.65}]},{"type":"PMHWAAX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.9,"w":4.4,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":6.5,"w":3.2,"h":4.3,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"PLOWBLD","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.2}]}]}]}},{"id":"DOGLOGG","name":"Doglogger/Sjostrum Trailers (decked)","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"l":13.5}]}]}]}},{"id":"GRTBBUK","name":"Grader, Tractor Blades, Buckets","size":{"powerUnits":[{"type":"GRADERS","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":4.4}]}]}]}},{"id":"HAYRACK","name":"Hayrack Semi-Trailer with a Folded Chassis/Empty Piggyback","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STLOGNG","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"h":4.15}]}]}]}},{"id":"IMCONTN","name":"Intermodal Containers","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"h":4.4,"l":26}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"h":4.4,"l":27.5}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"h":4.4}]}]},{"type":"REGTRCK","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"h":4.4}]}]}]}},{"id":"IMCONWS","name":"Intermodal Containers without Sides","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":4.4,"h":4.72}]}]}]}},{"id":"LPBOOMS","name":"Logs, Poles And Boomsticks (Up To 20.1)","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":2.9,"l":27.5}]},{"type":"LOGLGCY","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":5,"w":2.6,"l":25}]},{"type":"POLETRL","jeep":true,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":2.9,"l":27.5}]},{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":2.9,"l":26}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":2.9,"l":27.5}]},{"type":"HIBOFLT","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":2.9,"l":25}]},{"type":"STLOGNG","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":2.9,"l":25}]}]}]}},{"id":"LPBOOML","name":"Logs, Poles And Boomsticks (Over 20.1)","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"LOGFULL","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":10,"rp":10,"l":40}]},{"type":"POLETRL","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":8,"rp":9,"l":40}]},{"type":"STROPRT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":10,"l":40}]},{"type":"STRSELF","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":10,"l":36}]}]}]}},{"id":"MFHOMES","name":"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"SEMITRL","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":7.5,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":7.5,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]}]},{"type":"REGTRCK","trailers":[{"type":"DOLLIES","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"FLOATTR","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.57,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]}]}]}},{"id":"MFHOMEL","name":"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"SEMITRL","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":7.5,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":7.5,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]}]},{"type":"REGTRCK","trailers":[{"type":"DOLLIES","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"FLOATTR","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.57,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":1,"rp":6.9,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33,"l":36}]}]}]}]}},{"id":"PARKMHS","name":"Park Model Homes","size":{"powerUnits":[{"type":"REGTRCK","trailers":[{"type":"DOLLIES","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.9,"w":4.4,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FLOATTR","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.9,"w":4.4,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.9,"w":4.4,"h":4.88,"l":31.5,"regions":[{"region":"PCE","h":5.33}]}]}]}]}},{"id":"PIPESTL","name":"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"h":4.3,"l":31}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"h":4.3,"l":27.5}]},{"type":"STSTEER","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"h":4.3,"l":36}]},{"type":"STROPRT","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"h":4.3,"l":40}]}]}]}},{"id":"REDUCBL","name":"Reducible Loads","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STLOGNG","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.15,"l":27.5}]},{"type":"PLATFRM","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"SEMITRL","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":26,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSTEER","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWHELR","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWIDWH","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STCRANE","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STROPRT","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.3,"l":40,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STRSELF","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.3,"l":36,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"PICKRTT","trailers":[{"type":"SEMITRL","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":3.2,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"STINGER","trailers":[{"type":"SEMITRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":1.2,"h":4.3,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSTNGR","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":1.2,"h":4.3,"l":25,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"REGTRCK","trailers":[{"type":"DOLLIES","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":3.2,"h":4.3,"l":25,"regions":[{"region":"PCE","h":4.4}]}]},{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":3.2,"h":4.3,"l":25,"regions":[{"region":"PCE","h":4.4}]}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":3.2,"h":4.3,"l":16,"regions":[{"region":"PCE","h":4.4}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":3.2,"h":4.3,"l":25,"regions":[{"region":"PCE","h":4.4}]}]}]}]}},{"id":"SCRAPER","name":"Scraper on Dollies","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"DOLLIES","jeep":true,"booster":false,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.3,"l":25,"regions":[{"region":"PCE","h":4.4}]}]}]}]}},{"id":"OILFILD","name":"Oil Field Equipment","size":{"powerUnits":[{"type":"OGBEDTK","trailers":[{"type":"EXPANDO","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"h":4.3,"l":27.5}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"w":3.3,"h":4.3,"l":14}]},{"type":"OGOSFDT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.3,"h":4.3,"l":23}]}]},{"type":"OGOILSW","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"w":3.2,"h":4.3,"l":15}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"h":4.3,"l":25}]}]},{"type":"OGSERVC","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":3.8,"h":4.3,"l":15}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":3.8,"h":4.3,"l":23}]}]},{"type":"OGSRRAH","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":2.9,"h":4.15,"l":15.5}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":10,"rp":6.5,"w":2.9,"h":4.15,"l":23}]}]},{"type":"TRKTRAC","trailers":[{"type":"OGOSFDT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.3,"h":4.3,"l":23}]}]}]}},{"id":"JPTRLOG","name":"Tandem Jeep/Pole Trailer Loaded on Logging Truck","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"w":2.9,"h":4.3}]}]},{"type":"REGTRCK","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"w":2.9,"h":4.3}]}]}]}},{"id":"TOWDISB","name":"Tow Trucks And Disabled Vehicles","size":{"powerUnits":[{"type":"TOWVEHC","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"h":4.3,"l":27.5}]}]}]}},{"id":"TRQDLOG","name":"Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"h":4.3,"l":13.5}]}]},{"type":"REGTRCK","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":5,"h":4.3,"l":13.5}]}]}]}},{"id":"WOODCHP","name":"Wood Chips, Residuals","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"h":4.45,"l":27.5}]}]}]}},{"id":"EMPTYXX","name":"Empty","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"LOGOWBK","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":0,"rp":0,"w":3.2}]},{"type":"PLATFRM","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"l":31}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"l":27.5}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"l":31}]},{"type":"STWHELR","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWIDWH","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STCRANE","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"PICKRTT","trailers":[{"type":"OGOSFDT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.2,"h":4.3,"l":23}]},{"type":"PLATFRM","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"l":25,"regions":[{"region":"PCE","l":27.5}]}]},{"type":"STWHELR","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWIDWH","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STCRANE","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":3.2,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]}]}]}},{"id":"GRBBINS","name":"Garbage Bins","size":{"powerUnits":[{"type":"REGTRCK","trailers":[{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5}]}]}]}},{"id":"LAMBEAM","name":"Laminated Beams","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"POLETRL","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"l":40}]},{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"l":31}]}]}]}},{"id":"HAYLREC","name":"Hay Bales Large Rectangular","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.05,"h":4.3,"l":26,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.05,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.05,"h":4.4,"regions":[{"region":"LMN","h":4.3},{"region":"KTN","h":4.3},{"region":"PCE","h":4.8}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]}]},{"type":"REGTRCK","trailers":[{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]}]}]}},{"id":"HAYROND","name":"Hay Bales Round","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.5,"h":4.3,"l":26,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.5,"h":4.3,"l":27.5,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]}]},{"type":"REGTRCK","trailers":[{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]}]}]}},{"id":"HAYSREC","name":"Hay Bales Small Rectangular","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.05,"h":4.3,"l":26,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.05,"h":4.3,"l":27.5,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]}]},{"type":"REGTRCK","trailers":[{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.05,"h":4.3,"regions":[{"region":"PCE","h":4.8}]}]}]}]}},{"id":"BRGBEAM","name":"Bridge Beams","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"POLETRL","jeep":true,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"l":31}]}]}]}},{"id":"NONREDU","name":"Non-Reducible Loads","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STLOGNG","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"w":3.8,"h":4.15,"l":27.5}]},{"type":"PLATFRM","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"SEMITRL","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.4,"l":26,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.4,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.4,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.4,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSTEER","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWHELR","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWIDWH","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STCRANE","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STROPRT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":40,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STRSELF","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":36,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"PICKRTT","trailers":[{"type":"OGOSFDT","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.3,"h":4.3,"l":23}]},{"type":"SEMITRL","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":5,"h":4.88,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOEXP","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":5,"h":4.4,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"w":5,"h":4.4,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"w":5,"h":4.88,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSTEER","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWHELR","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STWIDWH","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STCRANE","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":27.5,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STROPRT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":40,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STRSELF","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"w":5,"h":4.88,"l":36,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"STINGER","trailers":[{"type":"SEMITRL","jeep":false,"booster":false,"selfIssue":false,"sizeDimensions":[{"fp":1,"rp":1.2,"h":4.88,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"STSTNGR","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":1.2,"h":4.88,"l":25,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"REGTRCK","trailers":[{"type":"DOLLIES","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":5,"h":4.88,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":5,"h":4.88,"l":25,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":5,"h":4.88,"l":16,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":6.5,"w":5,"h":4.88,"l":25,"regions":[{"region":"PCE","h":5.33}]}]}]}]}},{"id":"AUTOCRR","name":"Auto Carrier, Campers And Boats (Stinger Steered Transporters Only)","size":{"powerUnits":[{"type":"STINGER","trailers":[{"type":"STSTNGR","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":1,"rp":1.2,"h":4.4,"l":25,"regions":[{"region":"LMN","h":4.3},{"region":"KTN","h":4.3},{"region":"PCE","h":4.88}]}]}]}]}},{"id":"HAYRNPR","name":"Hay Bales (Round) Peace River Only","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"STACTRN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.5,"h":4.3,"l":26,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"STBTRAN","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"HIBOFLT","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]}]},{"type":"REGTRCK","trailers":[{"type":"FULLLTL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.5,"h":4.3,"regions":[{"region":"PCE","w":3.84,"h":4.8}]}]}]}]}},{"id":"BRSHCUT","name":"Brushcutters (Peace Only)","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"SEMITRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"regions":[{"region":"PCE","w":4.57,"h":5.33}]}]},{"type":"STSDBDK","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"regions":[{"region":"PCE","w":4.57,"h":5.33}]}]}]},{"type":"REGTRCK","trailers":[{"type":"NONEXXX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"regions":[{"region":"PCE","w":4.57,"h":5.33}]}]},{"type":"PONYTRL","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"regions":[{"region":"PCE","w":3.8,"h":5.33}]}]}]}]}},{"id":"FIXEDEQ","name":"Fixed Equipment","size":{"powerUnits":[{"type":"TRKTRAC","trailers":[{"type":"FECVYER","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":4,"rp":9.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FEDRMMX","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FEBGHSE","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":4.26,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FESEMTR","jeep":true,"booster":true,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FEWHELR","jeep":true,"booster":true,"selfIssue":false,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]}]},{"type":"REGTRCK","trailers":[{"type":"FECVYPT","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":4,"rp":9.5,"w":3.2,"h":4.3,"l":31}]},{"type":"FEDRMMX","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":3.8,"h":4.72,"l":31,"regions":[{"region":"PCE","h":5.33}]}]},{"type":"FEPNYTR","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"rp":4,"w":3.2,"h":4.3,"l":31}]},{"type":"FEBGHSE","jeep":false,"booster":false,"selfIssue":true,"sizeDimensions":[{"fp":3,"rp":6.5,"w":4.26,"h":4.72,"l":31}]}]}]}}],"globalSizeDefaults":{"fp":3,"rp":6.5,"w":2.6,"h":4.15,"l":31}}') +GO \ No newline at end of file diff --git a/database/mssql/scripts/utility/refresh-sample-data.sh b/database/mssql/scripts/utility/refresh-sample-data.sh index 022bf96f4..01b5b8ee1 100644 --- a/database/mssql/scripts/utility/refresh-sample-data.sh +++ b/database/mssql/scripts/utility/refresh-sample-data.sh @@ -14,6 +14,7 @@ sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -Q "SET NOCOUNT ON; DELETE FROM dbo.ORBC_COMPANY" sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -Q "SET NOCOUNT ON; DELETE FROM dbo.ORBC_CONTACT" sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -Q "SET NOCOUNT ON; DELETE FROM dbo.ORBC_ADDRESS" +sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -Q "SET NOCOUNT ON; DELETE FROM dbo.ORBC_POLICY_CONFIGURATION" echo "Finished deleting existing sample data" echo "Loading sample data...please wait" @@ -25,6 +26,7 @@ sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -i ${SCRIPT_DIR}/sampledata/dbo.ORBC_POWER_UNIT.Table.sql sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -i ${SCRIPT_DIR}/sampledata/dbo.ORBC_TRAILER.Table.sql sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -i ${SCRIPT_DIR}/sampledata/dbo.ORBC_FEATURE_FLAG.Table.sql +sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -i ${SCRIPT_DIR}/sampledata/dbo.ORBC_POLICY_CONFIGURATION.Table.sql echo "Setting credit account sequence restart to current timestamp (used only for lower environments)" sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -i ${SCRIPT_DIR}/sampledata/permit.ORBC_CREDIT_ACCOUNT_NUMBER_SEQ.sql diff --git a/policy-engine/docs/validation-result-reference.md b/policy-engine/docs/validation-result-reference.md new file mode 100644 index 000000000..005dfcfaa --- /dev/null +++ b/policy-engine/docs/validation-result-reference.md @@ -0,0 +1,89 @@ +# Policy Validation Result Reference + +## Validating a Permit Application +Permits are validated with the policy engine by supplying the permit JSON to the `validate` method of an instantiated policy object. A policy object is instantiated by passing a policy configuration JSON object to the `Policy` constructor. Refer to the Policy Configuration Reference documentation for information about the structure of the policy configuration JSON object. + +The `validate` method returns an object of type `ValidationResults` which has 5 properties: `violations`, `requirements`, `warnings`, `information`, and `cost`. Each of these properties is an array of `ValidationResult` objects. + +Currently in onroute only the `violations` and `cost` properties are used. In the future, `requirements`, `warnings`, and `information` will be added. For now those properties may be ignored. + +### Valid Permit Applications +A valid permit application is one whose `ValidationResults` has an empty `violations` array. That is, no policy violations were found in the permit application. Here is an example: + +```js +{ + "violations": [], + "requirements": [], + "warnings": [], + "information": [], + "cost": [ + { + "type": "cost", + "code": "cost-value", + "message": "Calculated permit cost", + "cost": 30 + } + ] +} +``` + +## Violations +If at least one `ValidationResult` is present in the `violations` property of the result, the permit application has failed the policy check. + +The `ValidationResult` will typically contain information about what the nature of the violation was. Here is an example: + +```js + "violations": [ + { + "type": "violation", + "code": "field-validation-error", + "message": "Duration must be in 30 day increments or a full year", + "fieldReference": "permitData.permitDuration" + } + ], +``` +In the example above, the permit duration in the permit application JSON was 31 days, which is invalid according to the rules specified for the permit type (TROS in this case). + +All of the properties of the violation are configured directly in the policy configuration JSON file, and are taken verbatim from there. To change the message presented for this violation, simply update the policy configuration JSON. + +### type +Violation `ValidationResult` objects will always have a type of `violation`. + +### code +The `code` property indicates the nature of the violation, which may be used by the calling application. For example, a `field-validation-error` may cause the frontend form to highlight the form element and present the message underneath in red text. + +### message +The `message` property is a friendly description of the cause of the violation. + +### fieldReference +The `fieldReference` property indicates the specific field which caused the violation, and can be used in conjunction with `code` to provide context to a user in the frontend. + +## Cost +To calculate the cost of a permit, validate it using the policy engine's `validate` method. The validation result will include a `cost` property specifying the cost of the permit. + +Any permit with a cost greater than zero will have at least one `ValidationResult` in the `cost` array of the `ValidationResults` object. Here is an example of a valid one year term oversize permit: + +```js +{ + "violations": [], + "requirements": [], + "warnings": [], + "information": [], + "cost": [ + { + "type": "cost", + "code": "cost-value", + "message": "Calculated permit cost", + "cost": 360 + } + ] +} +``` +The `type` will always be `cost`, `code` will always be `cost-value`, and `message` will always be `Calculated permit cost`. These may be ignored. + +The `cost` property indicates the permit cost in Canadian dollars, based on the details in the permit application itself. + +It is possible that there are multiple `cost` results. In this case, it is up to the calling application to add all of the `cost` properties together for a final permit cost. + +> [!NOTE] +> The reason there may be multiple `cost` results is that some permit types have both a fixed cost (minimum permit cost) as well as a cost based on kilometres driven. Since these are calculated independently they are added separately to the `cost` array. Typically there is no need for the onroute application to break down the permit cost into its constituent parts so only the sum is used. \ No newline at end of file diff --git a/policy-engine/src/_examples/validate-invalid-tros.ts b/policy-engine/src/_examples/validate-invalid-tros.ts new file mode 100644 index 000000000..c9c054a0f --- /dev/null +++ b/policy-engine/src/_examples/validate-invalid-tros.ts @@ -0,0 +1,22 @@ +import { Policy } from 'onroute-policy-engine'; +import { PermitAppInfo } from 'onroute-policy-engine/enum'; +import { masterPolicyConfig } from '../_test/policy-config/master.sample'; +import { validTros30Day } from '../_test/permit-app/valid-tros-30day'; +import dayjs from 'dayjs'; + +async function start() { + const policy: Policy = new Policy(masterPolicyConfig); + + // Set startDate to today + validTros30Day.permitData.startDate = dayjs().format( + PermitAppInfo.PermitDateFormat.toString(), + ); + + // Set duration to 31 days (an invalid duration) + validTros30Day.permitData.permitDuration = 31; + + const validationResult2 = await policy.validate(validTros30Day); + console.log(JSON.stringify(validationResult2, null, ' ')); +} + +start(); From 55176514e947722313f5e5bbd8e36e2a8d5c8130 Mon Sep 17 00:00:00 2001 From: zgong-gov <123983557+zgong-gov@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:06:36 -0700 Subject: [PATCH 03/58] ORV2-1504: Applying for no-fee permits (#1595) Co-authored-by: gchauhan-aot --- .../features/permits/helpers/feeSummary.ts | 6 ++ .../Amend/components/AmendPermitFinish.tsx | 1 + .../pages/Application/ApplicationReview.tsx | 16 +++++- .../components/form/PermitForm.tsx | 20 ++----- .../components/pay/ChoosePaymentMethod.scss | 1 + .../components/review/PermitReview.tsx | 15 +---- .../tests/ApplicationReview.test.tsx | 22 ++----- .../pages/ShoppingCart/ShoppingCartPage.scss | 1 + .../pages/ShoppingCart/ShoppingCartPage.tsx | 57 +++++++++++++------ .../components/ShoppingCartItem.tsx | 4 +- .../hooks/useCheckOutdatedCart.ts | 3 +- .../ShoppingCart/hooks/useShoppingCart.ts | 18 +++++- .../permits/pages/Void/FinishVoid.tsx | 4 +- .../pages/Void/components/VoidPermitForm.tsx | 22 ++++++- .../src/features/permits/types/CartItem.ts | 3 +- .../src/common/helper/permit-fee.helper.ts | 32 +++++++++-- .../payment/payment.service.ts | 33 ++++++++--- .../dto/response/read-shopping-cart.dto.ts | 8 +-- .../profile/shopping-cart.profile.ts | 4 +- 19 files changed, 180 insertions(+), 90 deletions(-) diff --git a/frontend/src/features/permits/helpers/feeSummary.ts b/frontend/src/features/permits/helpers/feeSummary.ts index 61927211b..560f96c85 100644 --- a/frontend/src/features/permits/helpers/feeSummary.ts +++ b/frontend/src/features/permits/helpers/feeSummary.ts @@ -107,6 +107,8 @@ export const calculateAmountToRefund = ( currPermitType: PermitType, ) => { const netPaid = calculateNetAmount(permitHistory); + if (isZeroAmount(netPaid)) return 0; // If total paid is $0 (eg. no-fee permits), then refund nothing + const feeForCurrDuration = calculateFeeByDuration(currPermitType, currDuration); return netPaid - feeForCurrDuration; }; @@ -128,12 +130,16 @@ export const isZeroAmount = (amount: number) => { */ export const calculateAmountForVoid = ( permit: Permit, + transactionHistory: PermitHistory[], ) => { const permitState = getPermitState(permit); if (permitState === PERMIT_STATES.EXPIRED) { return 0; } + const netAmountPaid = calculateNetAmount(transactionHistory); + if (isZeroAmount(netAmountPaid)) return 0; // If existing net paid is $0 (eg. no-fee permits), then refund nothing + const daysLeft = daysLeftBeforeExpiry(permit); const intervalDays = getDurationIntervalDays(permit.permitType); return calculateFeeByDuration( diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx index e3d0f8b0f..28cbe8d59 100644 --- a/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx +++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx @@ -18,6 +18,7 @@ import { DEFAULT_PERMIT_TYPE } from "../../../types/PermitType"; export const AmendPermitFinish = () => { const navigate = useNavigate(); const { companyId } = useParams(); + const { permit, amendmentApplication, diff --git a/frontend/src/features/permits/pages/Application/ApplicationReview.tsx b/frontend/src/features/permits/pages/Application/ApplicationReview.tsx index 87c926847..e6e04bbda 100644 --- a/frontend/src/features/permits/pages/Application/ApplicationReview.tsx +++ b/frontend/src/features/permits/pages/Application/ApplicationReview.tsx @@ -18,6 +18,8 @@ import { usePowerUnitSubTypesQuery } from "../../../manageVehicles/hooks/powerUn import { useTrailerSubTypesQuery } from "../../../manageVehicles/hooks/trailers"; import { useFetchSpecialAuthorizations } from "../../../settings/hooks/specialAuthorizations"; import { applyLCVToApplicationData } from "../../helpers/getDefaultApplicationFormData"; +import { calculateFeeByDuration } from "../../helpers/feeSummary"; +import { DEFAULT_PERMIT_TYPE } from "../../types/PermitType"; import { APPLICATIONS_ROUTES, APPLICATION_STEPS, @@ -38,11 +40,22 @@ export const ApplicationReview = () => { const { data: specialAuth } = useFetchSpecialAuthorizations(companyId); const isLcvDesignated = Boolean(specialAuth?.isLcvAllowed); + const isNoFeePermitType = Boolean(specialAuth?.noFeeType); const { data: companyInfo } = useCompanyInfoQuery(); const doingBusinessAs = companyInfo?.alternateName; - const applicationData = applyLCVToApplicationData(applicationContextData, isLcvDesignated); + const applicationData = applyLCVToApplicationData( + applicationContextData, + isLcvDesignated, + ); + + const fee = isNoFeePermitType + ? "0" + : `${calculateFeeByDuration( + getDefaultRequiredVal(DEFAULT_PERMIT_TYPE, applicationData?.permitType), + getDefaultRequiredVal(0, applicationData?.permitData?.permitDuration), + )}`; const { setSnackBar } = useContext(SnackBarContext); const { refetchCartCount } = useContext(CartContext); @@ -166,6 +179,7 @@ export const ApplicationReview = () => { applicationData?.permitData?.vehicleDetails?.saveVehicle } doingBusinessAs={doingBusinessAs} + calculatedFee={fee} /> diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx b/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx index 33e4c7d40..11356c1b1 100644 --- a/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx @@ -19,7 +19,6 @@ import { LCV_CONDITION } from "../../../../constants/constants"; import { sortConditions } from "../../../../helpers/conditions"; import { getStartOfDate } from "../../../../../../common/helpers/formatDate"; import { getExpiryDate } from "../../../../helpers/permitState"; -import { calculateFeeByDuration } from "../../../../helpers/feeSummary"; import { PowerUnit, Trailer, @@ -91,10 +90,6 @@ export const PermitForm = (props: PermitFormProps) => { setValue("permitData.expiryDate", dayjs(expiry)); }; - const handleSetFee = (fee: string) => { - setValue("permitData.feeSummary", fee); - }; - const isLcvDesignated = props.isLcvDesignated; const ineligiblePowerUnitSubtypes = getIneligiblePowerUnitSubtypes(permitType) .filter(subtype => !isLcvDesignated || !isVehicleSubtypeLCV(subtype.typeCode)); @@ -105,10 +100,7 @@ export const PermitForm = (props: PermitFormProps) => { handleSetExpiryDate(expiryDate); }, [expiryDate]); - // Update fee summary whenever duration or permit type changes - useEffect(() => { - handleSetFee(`${calculateFeeByDuration(permitType, permitDuration)}`); - }, [permitDuration, permitType]); + const isAmendAction = props.isAmendAction; const vehicleSubtype = vehicleFormData.vehicleSubType; useEffect(() => { @@ -117,7 +109,7 @@ export const PermitForm = (props: PermitFormProps) => { && permitConditions.some(({ condition }: PermitCondition) => condition === LCV_CONDITION.condition) ) { // If vehicle subtype in the form isn't LCV but conditions have LCV, - // then remove that LCV condition from the form + // then remove that LCV condition from the form handleSetConditions(permitConditions.filter( ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition, )); @@ -137,13 +129,13 @@ export const PermitForm = (props: PermitFormProps) => { @@ -154,7 +146,7 @@ export const PermitForm = (props: PermitFormProps) => { expiryDate={expiryDate} conditionsInPermit={permitConditions} durationOptions={props.durationOptions} - disableStartDate={props.isAmendAction} + disableStartDate={isAmendAction} permitType={permitType} pastStartDateStatus={props.pastStartDateStatus} includeLcvCondition={isLcvDesignated && isVehicleSubtypeLCV(vehicleFormData.vehicleSubType)} diff --git a/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.scss b/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.scss index 389c67953..d8ddd842d 100644 --- a/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.scss +++ b/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.scss @@ -1,5 +1,6 @@ .choose-payment-method { & &__title { + padding-top: 0; padding-bottom: 1.5rem; font-size: 1.5rem; } diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx index 95c79459f..054d334db 100644 --- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx @@ -12,9 +12,7 @@ import { ReviewFeeSummary } from "./ReviewFeeSummary"; import { ReviewActions } from "./ReviewActions"; import { CompanyProfile } from "../../../../../manageProfile/types/manageProfile"; import { VehicleSubType } from "../../../../../manageVehicles/types/Vehicle"; -import { DEFAULT_PERMIT_TYPE, PermitType } from "../../../../types/PermitType"; -import { calculateFeeByDuration } from "../../../../helpers/feeSummary"; -import { getDefaultRequiredVal } from "../../../../../../common/helpers/util"; +import { PermitType } from "../../../../types/PermitType"; import { Nullable } from "../../../../../../common/types/common"; import { PermitContactDetails } from "../../../../types/PermitContactDetails"; import { PermitVehicleDetails } from "../../../../types/PermitVehicleDetails"; @@ -48,18 +46,11 @@ interface PermitReviewProps { onAddToCart?: () => Promise; showChangedFields?: boolean; oldFields?: Nullable>; - calculatedFee?: Nullable; + calculatedFee: string; doingBusinessAs?: Nullable; } export const PermitReview = (props: PermitReviewProps) => { - const feeSummary = props.calculatedFee - ? props.calculatedFee - : `${calculateFeeByDuration( - getDefaultRequiredVal(DEFAULT_PERMIT_TYPE, props.permitType), - getDefaultRequiredVal(0, props.permitDuration), - )}`; - return ( @@ -108,7 +99,7 @@ export const PermitReview = (props: PermitReviewProps) => { isChecked={props.allChecked} setIsChecked={props.setAllChecked} permitType={props.permitType} - fee={feeSummary} + fee={props.calculatedFee} /> {props.children} diff --git a/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx b/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx index 52346fd91..09b10e1f6 100644 --- a/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx +++ b/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx @@ -408,24 +408,14 @@ describe("Review and Confirm Application Details", () => { it("should display proper fee summary", async () => { // Arrange and Act - const applicationData = { - ...defaultApplicationData, - permitData: { - ...defaultApplicationData.permitData, - feeSummary: `${calculateFeeByDuration( - defaultApplicationData.permitType, - defaultApplicationData.permitData.permitDuration, - )}`, - }, - }; - renderTestComponent(applicationData); + renderTestComponent(defaultApplicationData); // Assert - const { - permitType, - permitData: { feeSummary }, - } = applicationData; - const permitTypeStr = permitTypeDisplayText(permitType); + const feeSummary = `${calculateFeeByDuration( + defaultApplicationData.permitType, + defaultApplicationData.permitData.permitDuration, + )}`; + const permitTypeStr = permitTypeDisplayText(defaultApplicationData.permitType); expect(await feeSummaryPermitType()).toHaveTextContent(permitTypeStr); expect(await feeSummaryPrice()).toHaveTextContent(`$${feeSummary}.00`); expect(await feeSummaryTotal()).toHaveTextContent(`$${feeSummary}.00`); diff --git a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss index 14cbc61fe..769684187 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss +++ b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss @@ -22,6 +22,7 @@ background-color: $bc-white; display: flex; flex-direction: column; + padding-top: 1.5rem; .choose-payment-method { margin-bottom: 2.5rem; diff --git a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx index 6f16130a1..475c4a838 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx +++ b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx @@ -136,28 +136,32 @@ export const ShoppingCartPage = () => { }, []); useEffect(() => { + // transaction is undefined when payment endpoint has not been requested + // ie. "Pay Now" button has not been pressed if (typeof transaction !== "undefined") { - if (!isStaffActingAsCompany) { - // CV Client - if (!transaction?.url) { - // Failed to generate transaction url - navigate(SHOPPING_CART_ROUTES.DETAILS(true)); - } else { - window.open(transaction.url, "_self"); - } - } else if (!transaction) { - // Staff payment failed + if (!transaction) { + // Payment failed - ie. transaction object is null navigate(SHOPPING_CART_ROUTES.DETAILS(true)); - } else { - // Staff payment transaction created successfully, proceed to issue permit + } else if (isFeeZero || isStaffActingAsCompany) { + // If purchase was for no-fee permits, or if staff payment transaction was created successfully, + // simply proceed to issue permits issuePermitMutation.mutate([...selectedIds]); // also update the cart and cart count cartQuery.refetch(); refetchCartCount(); + } else { + // CV Client payment, anticipate PayBC transaction url + if (!transaction?.url) { + // Failed to generate transaction url + navigate(SHOPPING_CART_ROUTES.DETAILS(true)); + } else { + // Redirect to PayBC transaction url to continue payment + window.open(transaction.url, "_self"); + } } } - }, [transaction, isStaffActingAsCompany]); + }, [transaction, isStaffActingAsCompany, isFeeZero]); useEffect(() => { const issueFailed = hasPermitsActionFailed(issueResults); @@ -248,11 +252,30 @@ export const ShoppingCartPage = () => { }); }; + // Paying for no-fee permits + const handlePayForNoFee = () => { + startTransactionMutation.mutate({ + transactionTypeId: TRANSACTION_TYPES.P, + paymentMethodTypeCode: PAYMENT_METHOD_TYPE_CODE.NP, + applicationDetails: [ + ...selectedApplications.map((application) => ({ + applicationId: application.applicationId, + transactionAmount: 0, + })), + ], + }); + }; + const handlePay = (paymentMethodData: PaymentMethodData) => { if (startTransactionMutation.isPending) return; const { paymentMethod, additionalPaymentData } = paymentMethodData; + if (isFeeZero) { + handlePayForNoFee(); + return; + } + if (paymentMethod === PAYMENT_METHOD_TYPE_CODE.ICEPAY) { const { cardType, icepayTransactionId } = additionalPaymentData as IcepayPaymentData; @@ -365,9 +388,11 @@ export const ShoppingCartPage = () => { - + {!isFeeZero ? ( + + ) : null} {paymentFailed ? : null} diff --git a/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx b/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx index 694a17380..1689e3535 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx +++ b/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx @@ -1,7 +1,7 @@ import { Checkbox } from "@mui/material"; import "./ShoppingCartItem.scss"; -import { CartItem } from "../../../types/CartItem"; +import { SelectableCartItem } from "../../../types/CartItem"; import { DATE_FORMATS, toLocal } from "../../../../../common/helpers/formatDate"; import { CustomActionLink } from "../../../../../common/components/links/CustomActionLink"; import { feeSummaryDisplayText } from "../../../helpers/feeSummary"; @@ -14,7 +14,7 @@ export const ShoppingCartItem = ({ onDeselect, onEditCartItem, }: { - cartItemData: CartItem; + cartItemData: SelectableCartItem; isSelected: boolean; isDisabled?: boolean; onSelect: (id: string) => void; diff --git a/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts b/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts index 441e91f93..a3d54577e 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts +++ b/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts @@ -24,7 +24,6 @@ export const useCheckOutdatedCart = ( // Reset old cart items whenever radio button filter is changed setOldCartItems([]); }, [ - //showAllApplications cartFilterChanged ]); @@ -40,7 +39,7 @@ export const useCheckOutdatedCart = ( const outdatedApplicationNumbers = getOutdatedCartItems( oldCartItems, - getDefaultRequiredVal([], fetchedCartItems),//cartItems), + getDefaultRequiredVal([], fetchedCartItems), ).map(cartItem => cartItem.applicationNumber); return { diff --git a/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts b/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts index 7255e9793..652026301 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts +++ b/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts @@ -4,16 +4,28 @@ import { CartContext } from "../../../context/CartContext"; import { useFetchCart, useRemoveFromCart } from "../../../hooks/cart"; import { SelectableCartItem } from "../../../types/CartItem"; import { getDefaultRequiredVal } from "../../../../../common/helpers/util"; +import { useFetchSpecialAuthorizations } from "../../../../settings/hooks/specialAuthorizations"; +import { calculateFeeByDuration } from "../../../helpers/feeSummary"; export const useShoppingCart = ( companyId: string, enableCartFilter: boolean, ) => { const { refetchCartCount } = useContext(CartContext); + + // Cart filter state const [showAllApplications, setShowAllApplications] = useState(enableCartFilter); + + // Interacting with backend for cart const removeFromCartMutation = useRemoveFromCart(); const cartQuery = useFetchCart(companyId, showAllApplications); const { data: cartItems } = cartQuery; + + // Check if no-fee permit type is designated + const { data: specialAuth } = useFetchSpecialAuthorizations(companyId); + const isNoFeePermitType = Boolean(specialAuth?.noFeeType); + + // Cart item state const [cartItemSelection, setCartItemSelection] = useState([]); const cartItemsTotalCount = cartItemSelection.length; const selectedTotalFee = cartItemSelection @@ -35,9 +47,12 @@ export const useShoppingCart = ( ...cartItem, selected: true, // all selected by default isSelectable: true, // add user permission check (ie. CA can't select staff cart items) + fee: isNoFeePermitType + ? 0 + : calculateFeeByDuration(cartItem.permitType, cartItem.duration), })), ); - }, [cartItems]); + }, [cartItems, isNoFeePermitType]); const selectedItemsCount = cartItemSelection.filter(cartItem => cartItem.selected).length; @@ -88,6 +103,7 @@ export const useShoppingCart = ( cartItemSelection, selectedTotalFee, showAllApplications, + isNoFeePermitType, toggleSelectAll, handleCartFilterChange, handleSelectItem, diff --git a/frontend/src/features/permits/pages/Void/FinishVoid.tsx b/frontend/src/features/permits/pages/Void/FinishVoid.tsx index 80453e594..608a72907 100644 --- a/frontend/src/features/permits/pages/Void/FinishVoid.tsx +++ b/frontend/src/features/permits/pages/Void/FinishVoid.tsx @@ -42,9 +42,9 @@ export const FinishVoid = ({ isValidTransaction(history.paymentMethodTypeCode, history.pgApproved), ); - const amountToRefund = !permit + const amountToRefund = !permit || transactionHistory.length === 0 ? 0 - : -1 * calculateAmountForVoid(permit); + : -1 * calculateAmountForVoid(permit, transactionHistory); const { mutation: voidPermitMutation, voidResults } = useVoidPermit(); diff --git a/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx b/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx index c82465f3d..68128c983 100644 --- a/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx +++ b/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx @@ -15,7 +15,8 @@ import { useVoidPermit } from "../hooks/useVoidPermit"; import { mapToRevokeRequestData } from "../helpers/mapper"; import { Nullable } from "../../../../../common/types/common"; import { hasPermitsActionFailed } from "../../../helpers/permitState"; -import { getDefaultRequiredVal } from "../../../../../common/helpers/util"; +import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../../common/helpers/util"; +import { usePermitHistoryQuery } from "../../../hooks/hooks"; import { CustomFormComponent, getErrorMessage, @@ -26,6 +27,7 @@ import { invalidPhoneLength, requiredMessage, } from "../../../../../common/helpers/validationMessages"; +import { isValidTransaction } from "../../../helpers/payment"; const FEATURE = "void-permit"; @@ -46,6 +48,22 @@ export const VoidPermitForm = ({ const { mutation: revokePermitMutation, voidResults } = useVoidPermit(); + const { data: permitHistory } = usePermitHistoryQuery( + permit?.originalPermitId, + applyWhenNotNullable( + id => `${id}`, + permit?.companyId, + ), + ); + + const transactionHistory = getDefaultRequiredVal([], permitHistory) + .filter((history) => + isValidTransaction(history.paymentMethodTypeCode, history.pgApproved), + ); + + const amountToRefund = !permit || transactionHistory.length === 0 + ? 0 : -1 * calculateAmountForVoid(permit, transactionHistory); + useEffect(() => { const revokeFailed = hasPermitsActionFailed(voidResults); if (revokeFailed) { @@ -57,8 +75,6 @@ export const VoidPermitForm = ({ } }, [voidResults]); - const amountToRefund = !permit ? 0 : -1 * calculateAmountForVoid(permit); - const { control, getValues, diff --git a/frontend/src/features/permits/types/CartItem.ts b/frontend/src/features/permits/types/CartItem.ts index afdc35d37..fdf8ffe94 100644 --- a/frontend/src/features/permits/types/CartItem.ts +++ b/frontend/src/features/permits/types/CartItem.ts @@ -17,12 +17,13 @@ export interface CartItem { plate: string; startDate: string; expiryDate: string; - fee: number; + duration: number; }; export interface SelectableCartItem extends CartItem { selected: boolean; isSelectable: boolean; + fee: number; } export interface CartActionResponse extends PermitsActionResponse {} diff --git a/vehicles/src/common/helper/permit-fee.helper.ts b/vehicles/src/common/helper/permit-fee.helper.ts index 76c84ec35..a774b0473 100644 --- a/vehicles/src/common/helper/permit-fee.helper.ts +++ b/vehicles/src/common/helper/permit-fee.helper.ts @@ -16,6 +16,7 @@ import { import { differenceBetween } from './date-time.helper'; import * as dayjs from 'dayjs'; import { ApplicationStatus } from '../enum/application-status.enum'; +import { Nullable } from '../types/common'; /** * Calculates the permit fee based on the application and old amount. @@ -25,7 +26,11 @@ import { ApplicationStatus } from '../enum/application-status.enum'; * @throws {NotAcceptableException} If the duration is invalid for TROS permit type. * @throws {BadRequestException} If the permit type is not recognized. */ -export const permitFee = (application: Permit, oldAmount?: number): number => { +export const permitFee = ( + application: Permit, + isNoFee?: Nullable, + oldAmount?: Nullable, +): number => { let duration = calculateDuration(application); switch (application.permitType) { case PermitType.TERM_OVERSIZE: { @@ -55,6 +60,7 @@ export const permitFee = (application: Permit, oldAmount?: number): number => { TROS_TERM, oldAmount, application.permitStatus, + isNoFee, ); } case PermitType.TERM_OVERWEIGHT: { @@ -84,6 +90,7 @@ export const permitFee = (application: Permit, oldAmount?: number): number => { TROW_TERM, oldAmount, application.permitStatus, + isNoFee, ); } default: @@ -147,14 +154,27 @@ export const currentPermitFee = ( duration: number, pricePerTerm: number, allowedPermitTerm: number, - oldAmount?: number, - permitStatus?: ApplicationStatus, + oldAmount?: Nullable, + permitStatus?: Nullable, + isNoFee?: Nullable, ): number => { - let permitTerms = Math.ceil(duration / allowedPermitTerm); // ex: if duraion is 40 days then charge for 60 days. + // Calculate the number of permit terms based on the duration + const permitTerms = + permitStatus === ApplicationStatus.VOIDED + ? Math.floor(duration / allowedPermitTerm) + : Math.ceil(duration / allowedPermitTerm); + + // Special fee calculation for void permit if (permitStatus === ApplicationStatus.VOIDED) { - permitTerms = Math.floor(duration / allowedPermitTerm); //ex: if duration is 40 days then refund only 30 days. - return pricePerTerm * permitTerms * -1; + // If the permit status is voided, return a refund of 0 for permit with no fees, or return the applicable refund amount + return oldAmount === 0 ? 0 : -pricePerTerm * permitTerms; } + // For non void new application (exclude amendment application), if no fee applies, set the price per term to 0 for new application + if ((isNoFee && oldAmount === undefined) || oldAmount === 0) + return 0; + if (oldAmount === undefined) + oldAmount = 0; + // Calculate fee for non void permit. return oldAmount > 0 ? pricePerTerm * permitTerms - oldAmount : pricePerTerm * permitTerms + oldAmount; diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts index 7a67ee750..c876c6148 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts @@ -62,6 +62,7 @@ import { throwUnprocessableEntityException, } from '../../../common/helper/exception.helper'; import { isFeatureEnabled } from '../../../common/helper/common.helper'; +import { SpecialAuth } from 'src/modules/special-auth/entities/special-auth.entity'; @Injectable() export class PaymentService { @@ -254,7 +255,9 @@ export class PaymentService { createTransactionDto?.paymentMethodTypeCode !== PaymentMethodTypeEnum.WEB && createTransactionDto?.paymentMethodTypeCode !== - PaymentMethodTypeEnum.ACCOUNT + PaymentMethodTypeEnum.ACCOUNT && + createTransactionDto?.paymentMethodTypeCode !== + PaymentMethodTypeEnum.NO_PAYMENT ) { throwUnprocessableEntityException( 'Invalid payment method type for the user', @@ -781,16 +784,30 @@ export class PaymentService { application.originalPermitId, queryRunner, ); - - if (application.permitStatus === ApplicationStatus.VOIDED) { - const newAmount = permitFee(application); - return newAmount; - } - const oldAmount = calculatePermitAmount(permitPaymentHistory); - const fee = permitFee(application, oldAmount); + const isNoFee = await this.findNoFee( + application.company.companyId, + queryRunner, + ); + const oldAmount = permitPaymentHistory.length > 0?calculatePermitAmount(permitPaymentHistory):undefined; + const fee = permitFee(application, isNoFee, oldAmount); return fee; } + @LogAsyncMethodExecution() + async findNoFee( + companyId: number, + queryRunner: QueryRunner, + ): Promise { + const specialAuth = await queryRunner.manager + .createQueryBuilder() + .select('specialAuth') + .from(SpecialAuth, 'specialAuth') + .innerJoinAndSelect('specialAuth.company', 'company') + .where('company.companyId = :companyId', { companyId: companyId }) + .getOne(); + return specialAuth ? (specialAuth.noFeeType ? true : false) : false; + } + @LogAsyncMethodExecution() async findPermitHistory( originalPermitId: string, diff --git a/vehicles/src/modules/shopping-cart/dto/response/read-shopping-cart.dto.ts b/vehicles/src/modules/shopping-cart/dto/response/read-shopping-cart.dto.ts index 6d1a07d36..1329c9050 100644 --- a/vehicles/src/modules/shopping-cart/dto/response/read-shopping-cart.dto.ts +++ b/vehicles/src/modules/shopping-cart/dto/response/read-shopping-cart.dto.ts @@ -77,14 +77,14 @@ export class ReadShoppingCartDto { @AutoMap() @ApiProperty({ - description: 'The permit start date.', + description: 'The permit expiry date.', example: '2023-07-04T19:12:22Z', }) expiryDate: string; @ApiProperty({ - description: 'The permit fee', - example: 200, + description: 'The permit duration', + example: 30, }) - fee: number; + duration: number; } diff --git a/vehicles/src/modules/shopping-cart/profile/shopping-cart.profile.ts b/vehicles/src/modules/shopping-cart/profile/shopping-cart.profile.ts index 785f23463..2edc05701 100644 --- a/vehicles/src/modules/shopping-cart/profile/shopping-cart.profile.ts +++ b/vehicles/src/modules/shopping-cart/profile/shopping-cart.profile.ts @@ -49,12 +49,12 @@ export class ShoppingCartProfile extends AutomapperProfile { mapFrom((s) => s?.applicationOwner?.userGUID), ), forMember( - (d) => d.fee, + (d) => d.duration, mapFrom((s) => { const parsedPermitData = JSON.parse( s?.permitData?.permitData, ) as PermitData; - return +parsedPermitData?.feeSummary; + return +parsedPermitData?.permitDuration; }), ), forMember( From aeb77707c8dd0a9e7cfcc41eaf04afbc9ddfd3d4 Mon Sep 17 00:00:00 2001 From: gchauhan-aot <113390759+gchauhan-aot@users.noreply.github.com> Date: Mon, 16 Sep 2024 10:34:44 -0600 Subject: [PATCH 04/58] ORV2 1504 Sonar Issue Fix (#1597) Co-authored-by: zgong-gov --- .../permit-application-payment/payment/payment.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts index c876c6148..d0af3097b 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts @@ -805,7 +805,7 @@ export class PaymentService { .innerJoinAndSelect('specialAuth.company', 'company') .where('company.companyId = :companyId', { companyId: companyId }) .getOne(); - return specialAuth ? (specialAuth.noFeeType ? true : false) : false; + return !!specialAuth && !!specialAuth.noFeeType; } @LogAsyncMethodExecution() From b86eeb4c2345db001046bc5f918750661916b154 Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:04:28 -0700 Subject: [PATCH 05/58] ORV2-2824 - FE: Update status - Change leave application warning modal (#1598) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../permits/components/dialog/LeaveApplicationDialog.scss | 4 ++-- .../permits/components/dialog/LeaveApplicationDialog.tsx | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.scss b/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.scss index d29860836..930f7d6df 100644 --- a/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.scss +++ b/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.scss @@ -6,7 +6,7 @@ flex-direction: row; align-items: center; padding: 1em; - color: orbcStyles.$bc-messages-gold-text; + color: orbcStyles.$bc-black; background-color: orbcStyles.$bc-background-light-grey; font-size: 1.2rem; } @@ -19,7 +19,7 @@ justify-content: center; align-items: center; border-radius: 50%; - background-color: orbcStyles.$bc-messages-gold-text; + background-color: orbcStyles.$bc-black; margin-right: 0.5em; .warning-icon { diff --git a/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.tsx b/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.tsx index 394a8f858..546e248b7 100644 --- a/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.tsx +++ b/frontend/src/features/permits/components/dialog/LeaveApplicationDialog.tsx @@ -31,17 +31,14 @@ export const LeaveApplicationDialog = ({ Are you sure you want to leave?
-

- You have unsaved changes. If you leave, all your changes will be lost. -

-

This action cannot be undone.

+

Your application progress will be lost if you haven't saved.

+
+ + ); +}; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.scss b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.scss new file mode 100644 index 000000000..c9bb6ae56 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.scss @@ -0,0 +1,17 @@ +@import "../../../../themes/orbcStyles"; + +.permit-chip { + display: inline-block; + margin-left: 0.5rem; + + &--pending-review { + background-color: $bc-messages-blue-background; + color: $bc-primary-blue; + } + + &--in-review { + background-color: $bc-messages-gold-background; + color: $bc-messages-gold-text; + } + +} diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx new file mode 100644 index 000000000..38c57682e --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx @@ -0,0 +1,65 @@ +import "./ApplicationInReviewStatusChip.scss"; +import { OnRouteBCChip } from "../../../../common/components/chip/OnRouteBCChip"; + +import { + APPLICATION_QUEUE_STATUSES, + ApplicationQueueStatus, +} from "../../types/ApplicationQueueStatus"; + +/** + * Returns the theme name for the chip based on the permit status. + * If the permit is inactive or expired, a chip has to be displayed + * beside the permit number. + * @param applicationQueueStatus string representing the permit status + * @returns A string representing the theme name for the chip + */ +const getTheme = (applicationQueueStatus?: ApplicationQueueStatus) => { + switch (applicationQueueStatus) { + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "pending-review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "in-review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "closed"; + default: + return undefined; + } +}; + +/** + * Returns the text corresponding to the status of a permit. + * @param permitStatus string representing the permit status + * @returns Display text string corresponding to permit status + */ +const getStatusText = ( + applicationQueueStatus?: ApplicationQueueStatus, +): string => { + switch (applicationQueueStatus) { + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "Pending Review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "In Review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "Closed"; + default: + return ""; + } +}; +/** + * A simple chip component to be displayed beside the permit number. + */ +export const ApplicationInReviewStatusChip = ({ + applicationQueueStatus, +}: { + applicationQueueStatus?: ApplicationQueueStatus; +}) => { + const chipTheme = getTheme(applicationQueueStatus); + return chipTheme ? ( + + ) : null; +}; + +ApplicationInReviewStatusChip.displayName = "ApplicationInReviewStatusChip"; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.scss b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.scss new file mode 100644 index 000000000..f162b5da5 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.scss @@ -0,0 +1,24 @@ +@use "../list/List"; +@import "../../../../themes/orbcStyles"; + +.applications-in-review-list { + .applications-in-review-banner { + margin-bottom: 1.5rem; + width: 100%; + } + + & &__top-toolbar { + display: flex; + justify-content: flex-end; + margin-bottom: 1.5rem; + } + + & &__row { + &:hover { + // remove MRT row/cell color change on hover + td::after { + background-color: $white + } + } + } +} diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx new file mode 100644 index 000000000..f2896a377 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx @@ -0,0 +1,166 @@ +import { useCallback, useContext, useEffect, useState } from "react"; +import { RowSelectionState } from "@tanstack/table-core"; +import { + MaterialReactTable, + useMaterialReactTable, +} from "material-react-table"; + +import "./ApplicationsInReviewList.scss"; +import { ApplicationInReviewColumnDefinition } from "./ApplicationInReviewColumnDefinition"; +import { SnackBarContext } from "../../../../App"; +import { ApplicationListItem } from "../../types/application"; +import { NoRecordsFound } from "../../../../common/components/table/NoRecordsFound"; +import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; +import { + getDefaultNullableVal, + getDefaultRequiredVal, +} from "../../../../common/helpers/util"; +import { useApplicationsInQueueQuery } from "../../hooks/hooks"; +import { InfoBcGovBanner } from "../../../../common/components/banners/InfoBcGovBanner"; +import { + defaultTableInitialStateOptions, + defaultTableOptions, + defaultTableStateOptions, +} from "../../../../common/helpers/tableHelper"; +import { BANNER_MESSAGES } from "../../../../common/constants/bannerMessages"; +import { MRT_Row } from "material-react-table"; +import { ApplicationsInReviewRowOptions } from "./ApplicationsInReviewRowOptions"; +import { APPLICATION_QUEUE_STATUSES } from "../../types/ApplicationQueueStatus"; + +export const ApplicationsInReviewList = () => { + const { + applicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + } = useApplicationsInQueueQuery(); + + const { + data: applicationsInQueue, + isError, + isPending, + isFetching, + } = applicationsInQueueQuery; + + const [showAIRTable, setShowAIRTable] = useState(false); + + useEffect(() => { + const totalCount = getDefaultRequiredVal( + 0, + applicationsInQueue?.meta?.totalItems, + ); + setShowAIRTable(totalCount > 0); + }, [applicationsInQueue?.meta?.totalItems]); + + const { idirUserDetails, userDetails } = useContext(OnRouteBCContext); + const userRole = getDefaultNullableVal( + idirUserDetails?.userRole, + userDetails?.userRole, + ); + + const snackBar = useContext(SnackBarContext); + + const [rowSelection, setRowSelection] = useState({}); + + useEffect(() => { + if (isError) { + snackBar.setSnackBar({ + message: "An unexpected error occurred.", + showSnackbar: true, + setShowSnackbar: () => true, + alertType: "error", + }); + } + }, [isError]); + + const table = useMaterialReactTable({ + ...defaultTableOptions, + columns: ApplicationInReviewColumnDefinition, + data: getDefaultRequiredVal([], applicationsInQueue?.items), + initialState: { + ...defaultTableInitialStateOptions, + }, + state: { + ...defaultTableStateOptions, + showAlertBanner: isError, + showProgressBars: isFetching, + columnVisibility: { applicationId: true }, + isLoading: isPending, + rowSelection, + pagination, + sorting, + }, + layoutMode: "grid", + displayColumnDefOptions: { + "mrt-row-select": { + size: 10, + }, + "mrt-row-actions": { + header: "", + size: 40, + }, + }, + enableRowActions: true, + enableRowSelection: false, + onRowSelectionChange: useCallback(setRowSelection, [userRole]), + getRowId: (originalRow) => { + const applicationRow = originalRow as ApplicationListItem; + return applicationRow.permitId; + }, + renderTopToolbar: false, + enableGlobalFilter: false, + autoResetPageIndex: false, + manualFiltering: true, + manualPagination: true, + manualSorting: true, + rowCount: getDefaultRequiredVal(0, applicationsInQueue?.meta?.totalItems), + pageCount: getDefaultRequiredVal(0, applicationsInQueue?.meta?.pageCount), + onSortingChange: setSorting, + onPaginationChange: setPagination, + enablePagination: true, + enableBottomToolbar: true, + muiToolbarAlertBannerProps: isError + ? { + color: "error", + children: "Error loading data", + } + : undefined, + muiTableBodyRowProps: { + className: "applications-in-review-list__row", + }, + renderRowActions: useCallback( + ({ row }: { row: MRT_Row }) => { + return ( +
+ +
+ ); + }, + [], + ), + }); + + return ( + <> + {showAIRTable ? ( +
+ + + +
+ ) : ( + + )} + + ); +}; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx new file mode 100644 index 000000000..ceee9c676 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from "react"; +import { OnRouteBCTableRowActions } from "../../../../common/components/table/OnRouteBCTableRowActions"; +import { + useUpdateApplicationQueueStatusMutation, + useInvalidateApplicationsInQueue, +} from "../../hooks/hooks"; +import { CASE_ACTIVITY_TYPES } from "../../types/CaseActivityType"; +import { ApplicationInReviewModal } from "./ApplicationInReviewModal"; +import { useNavigate } from "react-router-dom"; +import { ERROR_ROUTES } from "../../../../routes/constants"; + +const PERMIT_ACTION_OPTION_TYPES = { + WITHDRAW_APPLICATION: "withdrawApplication", +} as const; + +type PermitActionOptionType = + (typeof PERMIT_ACTION_OPTION_TYPES)[keyof typeof PERMIT_ACTION_OPTION_TYPES]; + +const getOptionLabel = (optionType: PermitActionOptionType): string => { + if (optionType === PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION) { + return "Withdraw Application"; + } + + return ""; +}; + +const ALL_OPTIONS = [ + { + label: getOptionLabel(PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION), + value: PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION, + }, +]; + +const getOptions = (isInReview: boolean) => { + return ALL_OPTIONS.filter((option) => { + // Exclude 'WITHDRAW_APPLICATION' if 'isInReview' is false + if ( + isInReview && + option.value === PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION + ) { + return false; + } + return true; + }); +}; + +export const ApplicationsInReviewRowOptions = ({ + isInReview, + permitId, +}: { + isInReview: boolean; + permitId: string; +}) => { + const navigate = useNavigate(); + const { invalidate } = useInvalidateApplicationsInQueue(); + + const [isAIRModalOpen, setIsAIRModalOpen] = useState(false); + + const handleCloseAIRModal = () => { + setIsAIRModalOpen(false); + invalidate(); + }; + + const { mutateAsync, isError, error } = + useUpdateApplicationQueueStatusMutation(); + + useEffect(() => { + if (isError) { + // if the application has already been withdrawn by another user + if (error.response?.status === 422) { + return setIsAIRModalOpen(true); + } + // handle all other errors + navigate(ERROR_ROUTES.UNEXPECTED); + } + }, [isError, error]); + + /** + * Action handler upon a select event. + * @param selectedOption The option that was selected. + */ + const onSelectOptionCallback = (selectedOption: string) => { + if (selectedOption === PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION) { + mutateAsync({ + applicationId: permitId, + caseActivityType: CASE_ACTIVITY_TYPES.WITHDRAWN, + }); + } + }; + + return ( + <> + + + + + ); +}; diff --git a/frontend/src/features/permits/components/permit-list/PermitChip.tsx b/frontend/src/features/permits/components/permit-list/PermitChip.tsx index 332b955fb..d418882ac 100644 --- a/frontend/src/features/permits/components/permit-list/PermitChip.tsx +++ b/frontend/src/features/permits/components/permit-list/PermitChip.tsx @@ -5,6 +5,7 @@ import { PERMIT_STATUSES, isPermitInactive, } from "../../types/PermitStatus"; +import { APPLICATION_QUEUE_STATUSES } from "../../types/ApplicationQueueStatus"; /** * Returns the theme name for the chip based on the permit status. @@ -23,6 +24,12 @@ const getTheme = (permitStatus?: string) => { return "superseded"; case PERMIT_EXPIRED: return "expired"; + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "pending-review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "in-review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "closed"; default: return undefined; } @@ -43,6 +50,12 @@ const getStatusText = (permitStatus?: string): string => { return "Superseded"; case PERMIT_EXPIRED: return "Expired"; + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "Pending Review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "In Review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "Closed"; default: return ""; } diff --git a/frontend/src/features/permits/hooks/hooks.ts b/frontend/src/features/permits/hooks/hooks.ts index 218ae5a27..bfedb1884 100644 --- a/frontend/src/features/permits/hooks/hooks.ts +++ b/frontend/src/features/permits/hooks/hooks.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useContext } from "react"; import { AxiosError } from "axios"; import { MRT_PaginationState, MRT_SortingState } from "material-react-table"; import { @@ -33,7 +33,14 @@ import { getApplicationsInProgress, resendPermit, getPendingPermits, + getApplicationsInQueue, + updateApplicationQueueStatus, } from "../apiManager/permitsAPI"; +import { + CASE_ACTIVITY_TYPES, + CaseActivityType, +} from "../types/CaseActivityType"; +import { SnackBarContext } from "../../../App"; const QUERY_KEYS = { PERMIT_DETAIL: ( @@ -333,10 +340,16 @@ export const useAmendPermit = (companyIdParam?: Nullable) => { queryKey: QUERY_KEYS.PERMIT_DETAIL(data.permitId, companyIdParam), }); queryClient.invalidateQueries({ - queryKey: QUERY_KEYS.AMEND_APPLICATION(data.originalPermitId, companyIdParam), + queryKey: QUERY_KEYS.AMEND_APPLICATION( + data.originalPermitId, + companyIdParam, + ), }); queryClient.invalidateQueries({ - queryKey: QUERY_KEYS.PERMIT_HISTORY(data.originalPermitId, companyIdParam), + queryKey: QUERY_KEYS.PERMIT_HISTORY( + data.originalPermitId, + companyIdParam, + ), }); return { @@ -431,20 +444,28 @@ export const useApplicationsInProgressQuery = () => { }, ]); - const orderBy = sorting.length > 0 ? [ - { - column: sorting.at(0)?.id as string, - descending: Boolean(sorting.at(0)?.desc), - }, - ] : []; + const orderBy = + sorting.length > 0 + ? [ + { + column: sorting.at(0)?.id as string, + descending: Boolean(sorting.at(0)?.desc), + }, + ] + : []; const applicationsInProgressQuery = useQuery({ - queryKey: ["applicationsInProgress", pagination.pageIndex, pagination.pageSize, sorting], + queryKey: [ + "applicationsInProgress", + pagination.pageIndex, + pagination.pageSize, + sorting, + ], queryFn: () => getApplicationsInProgress({ page: pagination.pageIndex, take: pagination.pageSize, - orderBy, + orderBy, }), refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground refetchOnMount: "always", @@ -489,6 +510,104 @@ export const usePendingPermitsQuery = () => { }; }; +/** + * Hook that fetches applications in queue (PENDING_REVIEW, IN_REVIEW) and manages its pagination state. + * @returns Applications in queue along with pagination state and setter + */ +export const useApplicationsInQueueQuery = () => { + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const [sorting, setSorting] = useState([ + { + id: "updatedDateTime", + desc: true, + }, + ]); + + const orderBy = + sorting.length > 0 + ? [ + { + column: sorting.at(0)?.id as string, + descending: Boolean(sorting.at(0)?.desc), + }, + ] + : []; + + const applicationsInQueueQuery = useQuery({ + queryKey: [ + "applicationsInQueue", + pagination.pageIndex, + pagination.pageSize, + sorting, + ], + queryFn: () => + getApplicationsInQueue({ + page: pagination.pageIndex, + take: pagination.pageSize, + orderBy, + }), + refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground + refetchOnMount: "always", + placeholderData: keepPreviousData, + }); + + return { + applicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + }; +}; + +export const useUpdateApplicationQueueStatusMutation = () => { + const { invalidate } = useInvalidateApplicationsInQueue(); + const { setSnackBar } = useContext(SnackBarContext); + + return useMutation({ + mutationFn: (data: { + applicationId: string; + caseActivityType: CaseActivityType; + comment?: string; + }) => { + const { applicationId, caseActivityType, comment } = data; + + return updateApplicationQueueStatus( + applicationId, + caseActivityType, + comment, + ); + }, + onSuccess: (_data, variables) => { + const { caseActivityType } = variables; + if (caseActivityType === CASE_ACTIVITY_TYPES.WITHDRAWN) { + setSnackBar({ + showSnackbar: true, + setShowSnackbar: () => true, + message: "Withdrawn to Applications in Progress", + alertType: "info", + }); + invalidate(); + } + }, + onError: (err: AxiosError) => err, + }); +}; + +export const useInvalidateApplicationsInQueue = () => { + const queryClient = useQueryClient(); + + return { + invalidate: () => { + queryClient.invalidateQueries({ queryKey: ["applicationsInQueue"] }); + }, + }; +}; + /** * Hook used for resending a permit. * @returns Mutation object to be used for resending a permit diff --git a/frontend/src/features/permits/types/ApplicationQueueStatus.ts b/frontend/src/features/permits/types/ApplicationQueueStatus.ts new file mode 100644 index 000000000..e995ad16c --- /dev/null +++ b/frontend/src/features/permits/types/ApplicationQueueStatus.ts @@ -0,0 +1,8 @@ +export const APPLICATION_QUEUE_STATUSES = { + PENDING_REVIEW: "PENDING_REVIEW", + IN_REVIEW: "IN_REVIEW", + CLOSED: "CLOSED", +} as const; + +export type ApplicationQueueStatus = + (typeof APPLICATION_QUEUE_STATUSES)[keyof typeof APPLICATION_QUEUE_STATUSES]; diff --git a/frontend/src/features/permits/types/CaseActivityType.ts b/frontend/src/features/permits/types/CaseActivityType.ts new file mode 100644 index 000000000..d5c9bff15 --- /dev/null +++ b/frontend/src/features/permits/types/CaseActivityType.ts @@ -0,0 +1,16 @@ +export const APPLICATION_QUEUE_STATUSES = { + PENDING_REVIEW: "PENDING_REVIEW", + IN_REVIEW: "IN_REVIEW", + CLOSED: "CLOSED", +} as const; + +export type ApplicationQueueStatus = + (typeof APPLICATION_QUEUE_STATUSES)[keyof typeof APPLICATION_QUEUE_STATUSES]; + +export const CASE_ACTIVITY_TYPES = { + APPROVED: "APPROVED", + REJECTED: "REJECTED", + WITHDRAWN: "WITHDRAWN", +}; +export type CaseActivityType = + (typeof CASE_ACTIVITY_TYPES)[keyof typeof CASE_ACTIVITY_TYPES]; diff --git a/frontend/src/features/permits/types/PermitStatus.ts b/frontend/src/features/permits/types/PermitStatus.ts index 55de5ec1b..c54146b2e 100644 --- a/frontend/src/features/permits/types/PermitStatus.ts +++ b/frontend/src/features/permits/types/PermitStatus.ts @@ -5,6 +5,7 @@ export const PERMIT_STATUSES = { IN_PROGRESS: "IN_PROGRESS", IN_CART: "IN_CART", REJECTED: "REJECTED", + IN_QUEUE: "IN_QUEUE", UNDER_REVIEW: "UNDER_REVIEW", WAITING_APPROVAL: "WAITING_APPROVAL", WAITING_PAYMENT: "WAITING_PAYMENT", diff --git a/frontend/src/features/permits/types/application.ts b/frontend/src/features/permits/types/application.ts index 2688b1752..102fb5992 100644 --- a/frontend/src/features/permits/types/application.ts +++ b/frontend/src/features/permits/types/application.ts @@ -7,6 +7,7 @@ import { Nullable } from "../../../common/types/common"; import { PermitApplicationOrigin } from "./PermitApplicationOrigin"; import { PermitApprovalSource } from "./PermitApprovalSource"; import { PermitData } from "./PermitData"; +import { ApplicationQueueStatus } from "./ApplicationQueueStatus"; /** * A partial permit type that consists of all common fields used for a permit. @@ -58,9 +59,8 @@ type TransformPermitData = { /** * Type for response data from fetching Application details. */ -export interface ApplicationResponseData extends TransformPermitData< - ReplaceDayjsWithString ->{}; +export interface ApplicationResponseData + extends TransformPermitData> {} /** * Type for create application request payload. @@ -76,7 +76,7 @@ export interface CreateApplicationRequestData { permitApplicationOrigin?: Nullable; permitData: ReplaceDayjsWithString; comment?: Nullable; -}; +} /** * Type for update application request payload. @@ -107,6 +107,7 @@ export interface ApplicationListItem { unitNumber?: Nullable; vin?: Nullable; plate?: Nullable; + applicationQueueStatus?: ApplicationQueueStatus; } /** diff --git a/frontend/src/features/permits/types/permit.ts b/frontend/src/features/permits/types/permit.ts index fcfa53e72..c85e85c70 100644 --- a/frontend/src/features/permits/types/permit.ts +++ b/frontend/src/features/permits/types/permit.ts @@ -14,10 +14,7 @@ import { PermitApplicationOrigin } from "./PermitApplicationOrigin"; interface PartialPermit extends Omit< Required, - "previousRevision" - | "comment" - | "userGuid" - | "documentId" + "previousRevision" | "comment" | "userGuid" | "documentId" > { previousRevision?: Nullable; comment?: Nullable; @@ -68,7 +65,7 @@ export interface PermitListItem { /** * Type for permit response data from fetching permit details. */ -export interface PermitResponseData extends Permit {}; +export interface PermitResponseData extends Permit {} /** * Type used to describe the response object from various actions performed on permits @@ -82,4 +79,4 @@ export interface PermitsActionResponse { /** * Type used to describe the response object for issuing permits. */ -export interface IssuePermitsResponse extends PermitsActionResponse {}; +export interface IssuePermitsResponse extends PermitsActionResponse {} From c084e4ad801eaae06277cf6731c017770d1d6b9f Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:39:26 -0700 Subject: [PATCH 08/58] ORV2-2825 - FE: Update: Display suspend status on Staff search for company results (#1599) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../components/IDIRCompanySearchResults.scss | 6 ++++++ .../components/IDIRCompanySearchResults.tsx | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/idir/search/components/IDIRCompanySearchResults.scss b/frontend/src/features/idir/search/components/IDIRCompanySearchResults.scss index 3e2c690b2..08bc8fca8 100644 --- a/frontend/src/features/idir/search/components/IDIRCompanySearchResults.scss +++ b/frontend/src/features/idir/search/components/IDIRCompanySearchResults.scss @@ -41,4 +41,10 @@ text-decoration: none; } } + + &__cell { + .status-chip { + margin-left: 0.5rem; + } + } } diff --git a/frontend/src/features/idir/search/components/IDIRCompanySearchResults.tsx b/frontend/src/features/idir/search/components/IDIRCompanySearchResults.tsx index 68a0b4252..0077c5f78 100644 --- a/frontend/src/features/idir/search/components/IDIRCompanySearchResults.tsx +++ b/frontend/src/features/idir/search/components/IDIRCompanySearchResults.tsx @@ -25,6 +25,7 @@ import { Box, CardMedia, Stack, Typography } from "@mui/material"; import { CustomActionLink } from "../../../../common/components/links/CustomActionLink"; import { useNavigate } from "react-router-dom"; import { VerifiedClient } from "../../../../common/authentication/types"; +import { StatusChip } from "../../../settings/components/creditAccount/StatusChip"; /* * @@ -158,13 +159,18 @@ export const IDIRCompanySearchResults = memo( header: "Company Name", enableSorting: true, sortingFn: "alphanumeric", + minSize: 220, Cell: (props: { row: any; cell: any }) => { + const isCompanySuspended = props.row.original.isSuspended; return ( - onClickCompany(props.row.original)} - > - {props.row.original.legalName} - + <> + onClickCompany(props.row.original)} + > + {props.row.original.legalName} + + {isCompanySuspended && } + ); }, }, @@ -207,6 +213,9 @@ export const IDIRCompanySearchResults = memo( children: "Error loading data", } : undefined, + muiTableBodyCellProps: { + className: "idir-company-search-results__cell", + }, }); return ( From fdc89b152d7bb44210eedba49a6b9dbe94781372 Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:21:15 -0700 Subject: [PATCH 09/58] ORV2-2827 - FE: Update: Remove Applications in Progress Counter (#1600) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../src/common/components/tabs/TabsList.scss | 1 - .../src/common/components/tabs/TabsList.tsx | 44 ++++++++++++------- .../components/dashboard/PermitLists.tsx | 16 +------ .../ApplicationsInProgressList.tsx | 7 +-- 4 files changed, 31 insertions(+), 37 deletions(-) diff --git a/frontend/src/common/components/tabs/TabsList.scss b/frontend/src/common/components/tabs/TabsList.scss index 76796bde9..0f3faf35f 100644 --- a/frontend/src/common/components/tabs/TabsList.scss +++ b/frontend/src/common/components/tabs/TabsList.scss @@ -26,7 +26,6 @@ opacity: 1; padding: 0.875rem 0; } - &__count { font-size: 0.875rem; color: $bc-black; diff --git a/frontend/src/common/components/tabs/TabsList.tsx b/frontend/src/common/components/tabs/TabsList.tsx index 8171e20b2..47a48170c 100644 --- a/frontend/src/common/components/tabs/TabsList.tsx +++ b/frontend/src/common/components/tabs/TabsList.tsx @@ -1,4 +1,4 @@ -import { Chip, Tab, Tabs } from "@mui/material"; +import { Tab, Tabs } from "@mui/material"; import "./TabsList.scss"; import { TabComponentProps } from "./types/TabComponentProps"; @@ -28,21 +28,33 @@ export const TabsList = ({ scrollButtons="auto" aria-label="scrollable profile tabs" > - {componentList.map(({ label, count }, index) => { - return ( - -
{label}
- {count ? : null} - - } - {...TabProps(index)} - /> - ); - })} + {componentList.map( + ( + { + label, + // TODO remove this if we no longer need tab counters + // count + }, + index, + ) => { + return ( + +
{label}
+ { + // TODO remove this if we no longer need tab counters + /* {count ? : null} */ + } + + } + {...TabProps(index)} + /> + ); + }, + )} ); }; diff --git a/frontend/src/features/permits/components/dashboard/PermitLists.tsx b/frontend/src/features/permits/components/dashboard/PermitLists.tsx index 41f171896..a888cb4e5 100644 --- a/frontend/src/features/permits/components/dashboard/PermitLists.tsx +++ b/frontend/src/features/permits/components/dashboard/PermitLists.tsx @@ -1,21 +1,14 @@ -import React, { useState } from "react"; - +import React from "react"; import { TabLayout } from "../../../../common/components/dashboard/TabLayout"; import { StartApplicationAction } from "../../pages/Application/components/dashboard/StartApplicationAction"; import { ActivePermitList } from "../permit-list/ActivePermitList"; import { ExpiredPermitList } from "../permit-list/ExpiredPermitList"; import { ApplicationsInProgressList } from "../permit-list/ApplicationsInProgressList"; import { ApplicationsInReviewList } from "../permit-list/ApplicationsInReviewList"; -import { Nullable } from "../../../../common/types/common"; import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; import { RenderIf } from "../../../../common/components/reusable/RenderIf"; export const PermitLists = React.memo(() => { - const [applicationsInProgressCount, setApplicationsInProgressCount] = - useState>(); - const handleApplicationsCountChange = (count: number) => { - setApplicationsInProgressCount(count); - }; const tabs = []; const showApplicationsInProgressTab = usePermissionMatrix({ @@ -28,12 +21,7 @@ export const PermitLists = React.memo(() => { if (showApplicationsInProgressTab) { tabs.push({ label: "Applications in Progress", - count: applicationsInProgressCount, - component: ( - - ), + component: , }); } diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx index 530e7e90c..9f5c83c12 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx @@ -42,11 +42,7 @@ const getColumns = ( return ApplicationInProgressColumnDefinition(userRole); }; -export const ApplicationsInProgressList = ({ - onCountChange, -}: { - onCountChange: (count: number) => void; -}) => { +export const ApplicationsInProgressList = () => { const { applicationsInProgressQuery, pagination, @@ -82,7 +78,6 @@ export const ApplicationsInProgressList = ({ applicationsInProgress?.meta?.totalItems, ); setShowAIPTable(totalCount > 0); - onCountChange(totalCount); }, [applicationsInProgress?.meta?.totalItems]); const { idirUserDetails, userDetails } = useContext(OnRouteBCContext); From 84c5625949851a5d1129fd5c6c592fe78318a0d0 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:35:13 -0400 Subject: [PATCH 10/58] ORV2-2792 Staff Queue Changes (#1603) --- .../application-search.constraint.ts | 13 +++--- .../constraint/query-param-list.constraint.ts | 44 +++++++++++++++++++ .../common/enum/application-status.enum.ts | 2 +- .../src/common/enum/case-status-type.enum.ts | 36 +++++++++++++++ .../src/common/helper/permit-fee.helper.ts | 6 +-- .../application/application.controller.ts | 11 +++-- .../application/application.service.ts | 29 ++++++------ .../company-application.controller.ts | 10 ++++- .../getApplication.query-params.dto.ts | 30 ++++++------- .../profile/application.profile.ts | 31 ++++++------- .../payment/payment.service.ts | 5 ++- 11 files changed, 153 insertions(+), 64 deletions(-) create mode 100644 vehicles/src/common/constraint/query-param-list.constraint.ts diff --git a/vehicles/src/common/constraint/application-search.constraint.ts b/vehicles/src/common/constraint/application-search.constraint.ts index 8f367018e..1618c8e08 100644 --- a/vehicles/src/common/constraint/application-search.constraint.ts +++ b/vehicles/src/common/constraint/application-search.constraint.ts @@ -5,24 +5,25 @@ import { } from 'class-validator'; import { Nullable } from '../types/common'; import { ApplicationSearch } from '../enum/application-search.enum'; +import { ApplicationQueueStatus } from '../enum/case-status-type.enum'; @ValidatorConstraint({ name: 'ApplicationSearch', async: false }) export class ApplicationSearchConstraint implements ValidatorConstraintInterface { validate( - value: ApplicationSearch | boolean | undefined, + value: ApplicationSearch | boolean | undefined | ApplicationQueueStatus[], args: ValidationArguments, ) { const fields = args.object as { pendingPermits?: Nullable; - applicationsInQueue?: Nullable; + applicationQueueStatus?: Nullable; searchColumn?: Nullable; searchString?: Nullable; }; if ( fields.pendingPermits != undefined && - fields.applicationsInQueue !== undefined + fields.applicationQueueStatus?.length ) { return false; } else if (fields.searchColumn && !fields.searchString) { @@ -35,16 +36,16 @@ export class ApplicationSearchConstraint const message: string[] = []; const fields = args.object as { pendingPermits?: Nullable; - applicationsInQueue?: Nullable; + applicationQueueStatus?: Nullable; searchColumn?: Nullable; searchString?: Nullable; }; if ( fields.pendingPermits != undefined && - fields.applicationsInQueue !== undefined + fields.applicationQueueStatus?.length ) { message.push( - 'Both pendingPermits and applicationsInQueue cannot be set at the same time.', + 'Both pendingPermits and applicationQueueStatus cannot be set at the same time.', ); } if (fields.searchColumn && !fields.searchString) { diff --git a/vehicles/src/common/constraint/query-param-list.constraint.ts b/vehicles/src/common/constraint/query-param-list.constraint.ts new file mode 100644 index 000000000..68d5f9f5c --- /dev/null +++ b/vehicles/src/common/constraint/query-param-list.constraint.ts @@ -0,0 +1,44 @@ +import { + ValidatorConstraint, + ValidatorConstraintInterface, + ValidationArguments, +} from 'class-validator'; +import { Nullable } from '../types/common'; + +@ValidatorConstraint({ name: 'QueryParamList', async: false }) +export class QueryParamListConstraint implements ValidatorConstraintInterface { + validate(field: Nullable, args: ValidationArguments): boolean { + return !this.validateQueryParamList(field, args)?.length; + } + + validateQueryParamList( + field: Nullable, + args: ValidationArguments, + ): string[] { + const fieldList = field?.split(',') || []; + const allowedFieldValues = Object.values( + args.constraints?.at(0) as Record, + ); + + return fieldList.flatMap((fieldValue) => { + const errors: string[] = []; + if (!allowedFieldValues.includes(fieldValue)) { + errors.push( + `${fieldValue} is an invalid value. Possible values are: ${Object.values(allowedFieldValues).join(', ')}.`, + ); + } + return errors; + }); + } + + defaultMessage(args: ValidationArguments): string { + const applicationQueueStatus = ( + args.object as { applicationQueueStatus?: Nullable } + ).applicationQueueStatus; + const invalidFields = this.validateQueryParamList( + applicationQueueStatus, + args, + ); + return invalidFields.join(' '); + } +} diff --git a/vehicles/src/common/enum/application-status.enum.ts b/vehicles/src/common/enum/application-status.enum.ts index 7b5f5ea27..547905b96 100644 --- a/vehicles/src/common/enum/application-status.enum.ts +++ b/vehicles/src/common/enum/application-status.enum.ts @@ -36,7 +36,7 @@ export const ACTIVE_APPLICATION_STATUS: readonly ApplicationStatus[] = [ ]; /** - * Application statuses including Application In Progress (AIP) and Pending Permits/Applications + * Application statuses including Application In Progress (AIP), Pending Permits/Applications & IN_QUEUE */ export const ALL_APPLICATION_STATUS: readonly ApplicationStatus[] = [ ApplicationStatus.IN_PROGRESS, diff --git a/vehicles/src/common/enum/case-status-type.enum.ts b/vehicles/src/common/enum/case-status-type.enum.ts index 52e228044..f68f926e4 100644 --- a/vehicles/src/common/enum/case-status-type.enum.ts +++ b/vehicles/src/common/enum/case-status-type.enum.ts @@ -18,3 +18,39 @@ export type ApplicationQueueStatus = export const ACTIVE_APPLICATION_QUEUE_STATUS: readonly ApplicationQueueStatus[] = [ApplicationQueueStatus.PENDING_REVIEW, ApplicationQueueStatus.IN_REVIEW]; + +const statusMapping: Record = { + [ApplicationQueueStatus.PENDING_REVIEW]: CaseStatusType.OPEN, + [ApplicationQueueStatus.IN_REVIEW]: CaseStatusType.IN_PROGRESS, + [ApplicationQueueStatus.CLOSED]: CaseStatusType.CLOSED, +}; + +/** + * Converts an ApplicationQueueStatus to its corresponding CaseStatusType. + * + * @param status The ApplicationQueueStatus to convert. + * @returns The corresponding CaseStatusType. + */ +export const convertApplicationQueueStatus = ( + statuses: ApplicationQueueStatus[], +): CaseStatusType[] => { + return statuses?.map((status) => statusMapping[status]); +}; + +const reverseStatusMapping: Record = { + [CaseStatusType.OPEN]: ApplicationQueueStatus.PENDING_REVIEW, + [CaseStatusType.CLOSED]: ApplicationQueueStatus.CLOSED, + [CaseStatusType.IN_PROGRESS]: ApplicationQueueStatus.IN_REVIEW, +}; + +/** + * Converts an array of CaseStatusType values to their corresponding ApplicationQueueStatus values. + * + * @param statuses An array of CaseStatusType values to convert. + * @returns An array of ApplicationQueueStatus values + */ +export const convertCaseStatus = ( + statuses: CaseStatusType[], +): ApplicationQueueStatus[] => { + return statuses.map((status) => reverseStatusMapping[status]); +}; diff --git a/vehicles/src/common/helper/permit-fee.helper.ts b/vehicles/src/common/helper/permit-fee.helper.ts index a774b0473..835687f0c 100644 --- a/vehicles/src/common/helper/permit-fee.helper.ts +++ b/vehicles/src/common/helper/permit-fee.helper.ts @@ -170,10 +170,8 @@ export const currentPermitFee = ( return oldAmount === 0 ? 0 : -pricePerTerm * permitTerms; } // For non void new application (exclude amendment application), if no fee applies, set the price per term to 0 for new application - if ((isNoFee && oldAmount === undefined) || oldAmount === 0) - return 0; - if (oldAmount === undefined) - oldAmount = 0; + if ((isNoFee && oldAmount === undefined) || oldAmount === 0) return 0; + if (oldAmount === undefined) oldAmount = 0; // Calculate fee for non void permit. return oldAmount > 0 ? pricePerTerm * permitTerms - oldAmount diff --git a/vehicles/src/modules/permit-application-payment/application/application.controller.ts b/vehicles/src/modules/permit-application-payment/application/application.controller.ts index 7258c89e0..490a75f78 100644 --- a/vehicles/src/modules/permit-application-payment/application/application.controller.ts +++ b/vehicles/src/modules/permit-application-payment/application/application.controller.ts @@ -28,6 +28,10 @@ import { IDIR_USER_ROLE_LIST } from '../../../common/enum/user-role.enum'; import { PaginationDto } from '../../../common/dto/paginate/pagination'; import { ReadApplicationMetadataDto } from './dto/response/read-application-metadata.dto'; import { GetApplicationQueryParamsDto } from './dto/request/queryParam/getApplication.query-params.dto'; +import { + ApplicationQueueStatus, + convertApplicationQueueStatus, +} from '../../../common/enum/case-status-type.enum'; @ApiBearerAuth() @ApiTags('Application : API accessible exclusively to staff users and SA.') @@ -66,11 +70,10 @@ export class ApplicationController { orderBy, searchColumn, searchString, - applicationsInQueue, + applicationQueueStatus, }: GetApplicationQueryParamsDto, ): Promise> { const currentUser = request.user as IUserJWT; - return await this.applicationService.findAllApplications({ page, take, @@ -78,7 +81,9 @@ export class ApplicationController { currentUser, searchColumn, searchString, - applicationsInQueue, + applicationQueueStatus: convertApplicationQueueStatus( + (applicationQueueStatus?.split(',') as ApplicationQueueStatus[]) || [], + ), }); } diff --git a/vehicles/src/modules/permit-application-payment/application/application.service.ts b/vehicles/src/modules/permit-application-payment/application/application.service.ts index 4b5386a0e..933c9a226 100644 --- a/vehicles/src/modules/permit-application-payment/application/application.service.ts +++ b/vehicles/src/modules/permit-application-payment/application/application.service.ts @@ -296,7 +296,7 @@ export class ApplicationService { } /** - * Retrieves applications based on multiple optional filters including user GUID, company ID, pending permits status, applications in queue, and a search string. + * Retrieves applications based on multiple optional filters including user GUID, company ID, pending permits status, applications queue status, and a search string. * The function supports sorting by various columns and includes pagination for efficient retrieval. * @param findAllApplicationsOptions - Contains multiple optional parameters: pagination, sorting, filtering by company ID, user GUID, and other search filters. * - page: The current page number for pagination. @@ -306,7 +306,7 @@ export class ApplicationService { * - companyId: The ID of the company to filter applications by. * - userGUID: The GUID of the user whose applications to filter. * - currentUser: The current logged-in user's JWT payload. - * - applicationsInQueue: Boolean filter for applications that are in the queue. + * - applicationQueueStatus: Status filter for applications that are in the queue. * - searchColumn: The specific column to search within (e.g., plate, application number). * - searchString: The input keyword to use for searching. * @returns A paginated result containing filtered and sorted ReadApplicationMetadataDto objects. @@ -320,7 +320,7 @@ export class ApplicationService { companyId?: number; userGUID?: string; currentUser?: IUserJWT; - applicationsInQueue?: Nullable; + applicationQueueStatus?: Nullable; searchColumn?: Nullable; searchString?: Nullable; }): Promise> { @@ -332,7 +332,7 @@ export class ApplicationService { findAllApplicationsOptions.userGUID, findAllApplicationsOptions.searchColumn, findAllApplicationsOptions.searchString, - findAllApplicationsOptions.applicationsInQueue, + findAllApplicationsOptions.applicationQueueStatus, ); // total number of items const totalItems = await applicationsQB.getCount(); @@ -389,7 +389,8 @@ export class ApplicationService { currentUserRole: findAllApplicationsOptions?.currentUser?.orbcUserRole, currentDateTime: new Date(), - applicationsInQueue: findAllApplicationsOptions.applicationsInQueue, + applicationQueueStatus: + findAllApplicationsOptions.applicationQueueStatus, }), }, ); @@ -404,12 +405,12 @@ export class ApplicationService { userGUID?: string, searchColumn?: Nullable, searchString?: Nullable, - applicationsInQueue?: Nullable, + applicationQueueStatus?: Nullable, ): SelectQueryBuilder { - // Ensure that pendingPermits and applicationsInQueue are not set at the same time - if (pendingPermits !== undefined && applicationsInQueue !== undefined) { + // Ensure that pendingPermits and applicationQueueStatus are not set at the same time + if (pendingPermits !== undefined && applicationQueueStatus?.length) { throw new InternalServerErrorException( - 'Both pendingPermits and applicationsInQueue cannot be set at the same time.', + 'Both pendingPermits and applicationQueueStatus cannot be set at the same time.', ); } @@ -425,7 +426,7 @@ export class ApplicationService { ); // Include cases and the assigned case user only if applications are in queue - if (applicationsInQueue) { + if (applicationQueueStatus?.length) { permitsQuery = permitsQuery.innerJoinAndSelect('permit.cases', 'cases'); permitsQuery = permitsQuery.leftJoinAndSelect( 'cases.assignedUser', @@ -444,7 +445,7 @@ export class ApplicationService { } // Handle various status filters depending on the provided flags - if (applicationsInQueue) { + if (applicationQueueStatus?.length) { // If retrieving applications in queue, we filter those with "IN_QUEUE" status and open/in-progress cases permitsQuery = permitsQuery.andWhere( 'permit.permitStatus = :permitStatus', @@ -455,7 +456,7 @@ export class ApplicationService { permitsQuery = permitsQuery.andWhere( 'cases.caseStatusType IN (:...caseStatuses)', { - caseStatuses: [CaseStatusType.OPEN, CaseStatusType.IN_PROGRESS], + caseStatuses: applicationQueueStatus, }, ); } else if (pendingPermits) { @@ -467,7 +468,7 @@ export class ApplicationService { }); }), ); - } else if (pendingPermits === false || applicationsInQueue === false) { + } else if (pendingPermits === false) { // Filter active applications based on ACTIVE_APPLICATION_STATUS permitsQuery = permitsQuery.andWhere( new Brackets((qb) => { @@ -478,7 +479,7 @@ export class ApplicationService { ); } else if ( pendingPermits === undefined || - applicationsInQueue === undefined + !applicationQueueStatus?.length ) { // Filter all applications based on ALL_APPLICATION_STATUS permitsQuery = permitsQuery.andWhere( diff --git a/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts b/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts index cfdb8c294..0e674868b 100644 --- a/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts +++ b/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts @@ -48,6 +48,10 @@ import { ReadApplicationMetadataDto } from './dto/response/read-application-meta import { GetApplicationQueryParamsDto } from './dto/request/queryParam/getApplication.query-params.dto'; import { ApiPaginatedResponse } from 'src/common/decorator/api-paginate-response'; import { PermitReceiptDocumentService } from '../permit-receipt-document/permit-receipt-document.service'; +import { + ApplicationQueueStatus, + convertApplicationQueueStatus, +} from '../../../common/enum/case-status-type.enum'; @ApiBearerAuth() @ApiTags('Company Application') @@ -112,7 +116,11 @@ export class CompanyApplicationController { pendingPermits: getApplicationQueryParamsDto.pendingPermits, userGUID: userGuid, currentUser: currentUser, - applicationsInQueue: getApplicationQueryParamsDto.applicationsInQueue, + applicationQueueStatus: convertApplicationQueueStatus( + (getApplicationQueryParamsDto?.applicationQueueStatus?.split( + ',', + ) as ApplicationQueueStatus[]) || [], + ), searchColumn: getApplicationQueryParamsDto.searchColumn, searchString: getApplicationQueryParamsDto.searchString, }); diff --git a/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts b/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts index 33ef61b3e..5a3e90165 100644 --- a/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts +++ b/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts @@ -18,7 +18,8 @@ import { ALL_APPLICATION_STATUS, } from '../../../../../../common/enum/application-status.enum'; import { ApplicationSearchConstraint } from '../../../../../../common/constraint/application-search.constraint'; -import { ACTIVE_APPLICATION_QUEUE_STATUS } from '../../../../../../common/enum/case-status-type.enum'; +import { ApplicationQueueStatus } from '../../../../../../common/enum/case-status-type.enum'; +import { QueryParamListConstraint } from '../../../../../../common/constraint/query-param-list.constraint'; export class GetApplicationQueryParamsDto extends PageOptionsDto { @ApiProperty({ @@ -93,23 +94,20 @@ export class GetApplicationQueryParamsDto extends PageOptionsDto { pendingPermits?: Nullable; @ApiProperty({ + example: `${Object.values(ApplicationQueueStatus).join(',')}`, description: - `Setting this property true restricts the search results to those applications which are in queue (${Object.values(ACTIVE_APPLICATION_QUEUE_STATUS).join(', ')}). ` + - `Conversely, Setting it to false confines the search results to only those applications that are awaiting payment (${Object.values(ACTIVE_APPLICATION_STATUS).join(', ')}). ` + - `If left unspecified, the system will fetch all applications that are in any of the following statuses: ${Object.values(ALL_APPLICATION_STATUS).join(', ')}, including those awaiting issuance. ` + - 'Caution: You cannot set both pendingPermits and applicationsInQueue properties at the same time.', - example: true, + 'A string representing the application queue status order for query results. ' + + 'If unspecified, the results will not follow any specific order. ' + + 'The format involves a status code, with possible values being case-sensitive and must align with those defined in the schema. ' + + 'Start ordering using a status code, and optionally add more by joining them with commas. ' + + `Possible values are: ${Object.values(ApplicationQueueStatus).join(', ')}. ` + + 'Syntax: ', required: false, - type: 'boolean', + type: 'string', }) @IsOptional() - @Transform(({ obj, key }: { obj: Record; key: string }) => { - return obj[key] === 'true' ? true : obj[key] === 'false' ? false : obj[key]; - }) - @Validate(ApplicationSearchConstraint, { - message: - 'Both pendingPermits and applicationsInQueue cannot be set at the same time.', - }) - @IsBoolean() - applicationsInQueue?: Nullable; + @Validate(QueryParamListConstraint, [ApplicationQueueStatus]) + @IsString() + @Length(1, 150) + applicationQueueStatus?: Nullable; } diff --git a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts index d85a45442..1f63de139 100644 --- a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts +++ b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts @@ -23,8 +23,8 @@ import { Permit } from '../../permit/entities/permit.entity'; import { differenceBetween } from '../../../../common/helper/date-time.helper'; import { Nullable } from '../../../../common/types/common'; import { - ApplicationQueueStatus, CaseStatusType, + convertCaseStatus, } from '../../../../common/enum/case-status-type.enum'; import { ReadCaseActivityDto } from '../../../case-management/dto/response/read-case-activity.dto'; @@ -289,18 +289,13 @@ export class ApplicationProfile extends AutomapperProfile { ( s, { - applicationsInQueue, - }: { applicationsInQueue?: Nullable }, + applicationQueueStatus, + }: { applicationQueueStatus?: Nullable }, ) => { - if (applicationsInQueue && s.cases?.length) { - switch (s.cases?.at(0)?.caseStatusType) { - case CaseStatusType.OPEN: - return ApplicationQueueStatus.PENDING_REVIEW; - case CaseStatusType.IN_PROGRESS: - return ApplicationQueueStatus.IN_REVIEW; - case CaseStatusType.CLOSED: - return ApplicationQueueStatus.CLOSED; - } + if (applicationQueueStatus?.length && s.cases?.length) { + return convertCaseStatus([s.cases?.at(0)?.caseStatusType])?.at( + 0, + ); } }, ), @@ -313,15 +308,15 @@ export class ApplicationProfile extends AutomapperProfile { { currentUserRole, currentDateTime, - applicationsInQueue, + applicationQueueStatus, }: { currentUserRole: UserRole; currentDateTime: Date; - applicationsInQueue?: Nullable; + applicationQueueStatus?: Nullable; }, ) => { if ( - applicationsInQueue && + applicationQueueStatus?.length && doesUserHaveRole(currentUserRole, IDIR_USER_ROLE_LIST) ) { const diff = differenceBetween( @@ -346,14 +341,14 @@ export class ApplicationProfile extends AutomapperProfile { s, { currentUserRole, - applicationsInQueue, + applicationQueueStatus, }: { currentUserRole: UserRole; - applicationsInQueue?: Nullable; + applicationQueueStatus?: Nullable; }, ) => { if ( - applicationsInQueue && + applicationQueueStatus?.length && doesUserHaveRole(currentUserRole, IDIR_USER_ROLE_LIST) && s.cases?.length ) { diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts index d0af3097b..3c82633d9 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts @@ -788,7 +788,10 @@ export class PaymentService { application.company.companyId, queryRunner, ); - const oldAmount = permitPaymentHistory.length > 0?calculatePermitAmount(permitPaymentHistory):undefined; + const oldAmount = + permitPaymentHistory.length > 0 + ? calculatePermitAmount(permitPaymentHistory) + : undefined; const fee = permitFee(application, isNoFee, oldAmount); return fee; } From 56c437506be5e6dfc81440f0071a2f9b1f29b66b Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:22:27 -0400 Subject: [PATCH 11/58] fix: ORV2-2792 Document applicationQueueStatus query parameter (#1604) --- .../request/queryParam/getApplication.query-params.dto.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts b/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts index 5a3e90165..279c77a53 100644 --- a/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts +++ b/vehicles/src/modules/permit-application-payment/application/dto/request/queryParam/getApplication.query-params.dto.ts @@ -96,10 +96,9 @@ export class GetApplicationQueryParamsDto extends PageOptionsDto { @ApiProperty({ example: `${Object.values(ApplicationQueueStatus).join(',')}`, description: - 'A string representing the application queue status order for query results. ' + - 'If unspecified, the results will not follow any specific order. ' + - 'The format involves a status code, with possible values being case-sensitive and must align with those defined in the schema. ' + - 'Start ordering using a status code, and optionally add more by joining them with commas. ' + + 'The query parameter allows for filtering results based on applicationQueueStatus. ' + + 'Multiple application queue statuses can be specified and should be comma-separated. ' + + 'The values are case-sensitive and must match those defined in the schema. ' + `Possible values are: ${Object.values(ApplicationQueueStatus).join(', ')}. ` + 'Syntax: ', required: false, From 3599fd646480144ea13ce29e6a87bcf8cd16b0e4 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:22:06 -0400 Subject: [PATCH 12/58] fix: ORV2-2972 Start and Expiry date validation (#1606) --- .../application/application.service.ts | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/vehicles/src/modules/permit-application-payment/application/application.service.ts b/vehicles/src/modules/permit-application-payment/application/application.service.ts index 933c9a226..0db543da1 100644 --- a/vehicles/src/modules/permit-application-payment/application/application.service.ts +++ b/vehicles/src/modules/permit-application-payment/application/application.service.ts @@ -71,8 +71,12 @@ import { NotificationTemplate } from '../../../common/enum/notification-template import { PermitData } from '../../../common/interface/permit.template.interface'; import { ApplicationApprovedNotification } from '../../../common/interface/application-approved.notification.interface'; import { ApplicationRejectedNotification } from '../../../common/interface/application-rejected.notification.interface'; -import { convertUtcToPt } from '../../../common/helper/date-time.helper'; +import { + convertUtcToPt, + differenceBetween, +} from '../../../common/helper/date-time.helper'; import { ReadCaseActivityDto } from '../../case-management/dto/response/read-case-activity.dto'; +import * as dayjs from 'dayjs'; @Injectable() export class ApplicationService { @@ -558,13 +562,32 @@ export class ApplicationService { ): Promise { const existingApplication = await this.findOne(applicationId, companyId); - // Enforce that application is editable only if it is currently IN_PROGRESS - if (existingApplication.permitStatus !== ApplicationStatus.IN_PROGRESS) { + // Enforce that the application is editable only if it is currently IN_PROGRESS or if the user has an appropriate IDIR role and the application is IN_QUEUE + if ( + existingApplication.permitStatus !== ApplicationStatus.IN_PROGRESS && + !( + doesUserHaveRole(currentUser.orbcUserRole, IDIR_USER_ROLE_LIST) && + existingApplication.permitStatus === ApplicationStatus.IN_QUEUE + ) + ) { throw new BadRequestException( - 'Only an Application currently in progress can be modified.', + 'Only an Application currently in progress can be modified or must have correct authorization.', ); } + if ( + isPermitTypeEligibleForQueue(existingApplication.permitType) && + existingApplication.permitStatus === ApplicationStatus.IN_QUEUE + ) { + const permitData = JSON.parse( + existingApplication?.permitData?.permitData, + ) as PermitData; + const currentDate = dayjs(new Date().toISOString())?.format('YYYY-MM-DD'); + if (differenceBetween(permitData?.startDate, currentDate, 'days') < 0) { + throwUnprocessableEntityException('Start Date is in the past.'); + } + } + const newApplication = this.classMapper.map( updateApplicationDto, UpdateApplicationDto, @@ -1009,6 +1032,27 @@ export class ApplicationService { queryRunner, }); } else { + if (CaseActivityType.APPROVED === caseActivityType) { + const permitData = JSON.parse( + application?.permitData?.permitData, + ) as PermitData; + const currentDate = dayjs(new Date().toISOString())?.format( + 'YYYY-MM-DD', + ); + + if ( + application.permitStatus === ApplicationStatus.IN_QUEUE && + (differenceBetween(permitData?.startDate, currentDate, 'days') < + 0 || + differenceBetween(permitData?.expiryDate, currentDate, 'days') < + 0) + ) { + throwUnprocessableEntityException( + 'Start Date and/or Permit Expiry Date is in the past.', + ); + } + } + result = await this.caseManagementService.workflowEnd({ currentUser, applicationId, @@ -1094,7 +1138,9 @@ export class ApplicationService { await queryRunner.commitTransaction(); } } catch (error) { - await queryRunner.rollbackTransaction(); + if (queryRunner.isTransactionActive) { + await queryRunner.rollbackTransaction(); + } this.logger.error(error); //Swallow Notification error } From e0f581aba1cbb1dcc38cb618e25e0ccc2b4525f0 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:07:45 -0700 Subject: [PATCH 13/58] Feat/release please (#1602) --- .github/workflows/demo.yml | 44 +++++++----------- .github/workflows/dev.yml | 40 +++++++++++++++++ .github/workflows/merge.yml | 84 ++++++++--------------------------- .github/workflows/pr-open.yml | 22 ++------- .github/workflows/prod.yml | 34 ++++++++++++++ .github/workflows/release.yml | 40 +++++++++++++++++ .github/workflows/test.yml | 35 +++++++++++++++ .github/workflows/uat.yml | 41 +++++++---------- .gitignore | 1 + 9 files changed, 204 insertions(+), 137 deletions(-) create mode 100644 .github/workflows/dev.yml create mode 100644 .github/workflows/prod.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 73711c54e..60278a1e6 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -3,42 +3,32 @@ name: Deploy Demo on: workflow_dispatch: inputs: - environment: - description: "Deployment environment - test --> vault secrets" - required: true - type: choice - options: ["test","tools"] - default: "test" + tag: + description: "Image tag to deploy" + required: false + type: string + default: "uat" + workflow_call: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string + default: "uat" - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true - jobs: - uninstall-demo: - name: Uninstall (demo) - environment: ${{inputs.environment}} - runs-on: ubuntu-22.04 - steps: - - name: uninstall - run: | - oc login --token=${{ secrets.oc_token }} --server=${{ secrets.oc_server }} - oc project ${{ secrets.OC_NAMESPACE }} # Safeguard! - helm uninstall onroutebc-demo || true deploy-demo: - name: Deploys (demo) + name: Deploys (DEMO) uses: ./.github/workflows/deploy.yml - needs: uninstall-demo secrets: inherit with: autoscaling: false - environment: ${{inputs.environment}} - tag: "test" + environment: demo + tag: ${{inputs.tag}} release: "demo" params: | --set-string global.license='c28f0c' \ --set-string global.zone='test' \ --set-string global.vault.role='nonprod' \ - --set-string global.vault.zone='staging' \ - --set-string global.pr_num='${{ needs.vars.outputs.pr }}' \ + --set-string global.vault.zone='demo' \ + --set-string global.pr_num='${{ inputs.tag }}' \ diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 000000000..4f50802b3 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,40 @@ +--- +name: Deploy Dev + +on: + workflow_dispatch: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string + default: "latest" + workflow_call: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string + default: "latest" +jobs: + deploy-dev: + name: Deploys (DEV) + uses: ./.github/workflows/deploy.yml + secrets: inherit + with: + autoscaling: false + environment: dev + release: ${{inputs.tag}} + tag: ${{ inputs.tag }} + triggers: '' #omit=always; + params: | + --set-string global.license='c28f0c' \ + --set-string global.zone='dev' \ + --set-string global.vault.role='nonprod' \ + --set-string global.vault.zone='dev' \ + --set-string global.pr_num='${{ inputs.tag }}' \ + --set-json dops.containers[0].resources='{"limits": {"cpu": "1000m", "memory": "2000Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ + --set-json frontend.containers[0].resources='{"limits": {"cpu": "75m", "memory": "150Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ + --set-json scheduler.containers[0].resources='{"limits": {"cpu": "75m", "memory": "150Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ + --set-json policy.containers[0].resources='{"limits": {"cpu": "75m", "memory": "150Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ + --set-json vehicles.containers[0].resources='{"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "200m", "memory": "400Mi"}}' \ diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 71badf203..611023eb3 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -11,10 +11,11 @@ on: - '!.github/workflows/merge.yml' workflow_dispatch: inputs: - pr_no: - description: "PR-numbered container set to deploy" - type: number - required: true + pr: + description: "PR num of image to deploy" + required: false + type: string + concurrency: group: ${{ github.workflow }} @@ -29,27 +30,21 @@ jobs: timeout-minutes: 1 steps: # Get PR number for squash merges to main + - name: Get PR Number From Event + if: ${{ github.event_name == 'push' }} + id: pr_no + uses: bcgov-nr/action-get-pr@v0.0.1 - name: PR Number id: pr - uses: bcgov-nr/action-get-pr@v0.0.1 + run: echo pr=${{ steps.pr_no.outputs.pr || inputs.pr}} >> $GITHUB_OUTPUT deploys-test: - name: Deploys (test) + name: Deploys (Test) needs: [vars] - uses: ./.github/workflows/deploy.yml + uses: ./.github/workflows/test.yml secrets: inherit with: - autoscaling: true - environment: test - release: test tag: ${{ needs.vars.outputs.pr }} - params: | - --set-string global.license='c28f0c' \ - --set-string global.zone='test' \ - --set-string global.vault.role='nonprod' \ - --set-string global.vault.zone='test' \ - --set-string global.pr_num='${{ needs.vars.outputs.pr }}' \ - promote-images-test: name: Promote Images - Test @@ -69,57 +64,14 @@ jobs: target: ${{ needs.vars.outputs.pr }} tags: test #Promote images AFTER successful deploy - deploys-prod: - name: Deploys (prod) + release-please: + name: Release-Please needs: [promote-images-test, vars] - uses: ./.github/workflows/deploy.yml - secrets: inherit - with: - autoscaling: true - environment: prod - tag: ${{ needs.vars.outputs.pr }} - release: prod - params: | - --set-string global.license='c28f0c' \ - --set-string global.zone='prod' \ - --set-string global.vault.role='prod' \ - --set-string global.vault.zone='prod' \ - --set-string global.pr_num='${{ needs.vars.outputs.pr }}' \ - - promote-images-prod: - name: Promote Images - Prod - needs: [deploys-prod, vars] runs-on: ubuntu-22.04 - permissions: - packages: write - strategy: - matrix: - package: [dops, vehicles, frontend, scheduler, policy] timeout-minutes: 2 steps: - - uses: shrink/actions-docker-registry-tag@v4 + - uses: google-github-actions/release-please-action@v3 with: - registry: ghcr.io - repository: ${{ github.repository }}/${{ matrix.package }} - target: ${{ needs.vars.outputs.pr }} - tags: prod #Promote images AFTER successful deploy - - create-release: - name: Create release - runs-on: ubuntu-22.04 - needs: [deploys-prod, vars] - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Generate release tag - id: generate_release_tag - shell: bash - run: | - echo next_release_tag=$(expr $(echo $(curl https://api.github.com/repos/bcgov/onroutebc/releases/latest | grep tag_name | cut -d '-' -f3 - | cut -d '"' -f1 -) + 1)) >> $GITHUB_OUTPUT - - - name: Create Release - run: | - gh release create "${{ github.event.repository.name }}-release-${{ steps.generate_release_tag.outputs.next_release_tag }}" \ - --repo=${{ github.repository }} \ - --title="${{ github.event.repository.name }}-release-${{ steps.generate_release_tag.outputs.next_release_tag }}" \ - --generate-notes + release-type: simple + package-name: release-please-action + changelog-types: '[{"type":"ORV2","section":"Features","hidden":false},{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"build","section":"Miscellaneous","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"ci","section":"Miscellaneous","hidden":false},{"type":"docs","section":"Miscellaneous","hidden":false},{"type":"perf","section":"Miscellaneous","hidden":false},{"type":"refactor","section":"Miscellaneous","hidden":false},{"type":"revert","section":"Miscellaneous","hidden":false},{"type":"style","section":"Miscellaneous","hidden":false},{"type":"test","section":"Miscellaneous","hidden":false}]' diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index b7961312d..ea34523c5 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -74,26 +74,10 @@ jobs: triggers: '${{ matrix.package }}/' #omit to build everything # https://github.com/bcgov-nr/action-deployer-openshift - deploys: - name: Deploys + deploys-dev: + name: Deploys (Dev) needs: [builds, vars] - uses: ./.github/workflows/deploy.yml + uses: ./.github/workflows/dev.yml secrets: inherit with: - autoscaling: false - repository: ${{ github.event.repository.name }} - environment: dev - release: ${{ needs.vars.outputs.pr }} tag: ${{ needs.vars.outputs.pr }} - triggers: '' #omit=always; - params: | - --set-string global.license='c28f0c' \ - --set-string global.zone='dev' \ - --set-string global.vault.role='nonprod' \ - --set-string global.vault.zone='dev' \ - --set-string global.pr_num='${{ needs.vars.outputs.pr }}' \ - --set-json dops.containers[0].resources='{"limits": {"cpu": "1000m", "memory": "2000Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ - --set-json frontend.containers[0].resources='{"limits": {"cpu": "75m", "memory": "150Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ - --set-json scheduler.containers[0].resources='{"limits": {"cpu": "75m", "memory": "150Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ - --set-json policy.containers[0].resources='{"limits": {"cpu": "75m", "memory": "150Mi"}, "requests": {"cpu": "25m", "memory": "50Mi"}}' \ - --set-json vehicles.containers[0].resources='{"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "200m", "memory": "400Mi"}}' \ diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml new file mode 100644 index 000000000..f9da84bf7 --- /dev/null +++ b/.github/workflows/prod.yml @@ -0,0 +1,34 @@ +name: Deploy Prod + +on: + workflow_dispatch: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string + default: "prod" + workflow_call: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string + default: "prod" + +jobs: + deploys-prod: + name: Deploys (PROD) + uses: ./.github/workflows/deploy.yml + secrets: inherit + with: + autoscaling: true + environment: prod + tag: ${{ inputs.tag }} + release: prod + params: | + --set-string global.license='c28f0c' \ + --set-string global.zone='prod' \ + --set-string global.vault.role='prod' \ + --set-string global.vault.zone='prod' \ + --set-string global.pr_num='${{ inputs.tag }}' \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..01b4dd1b2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,40 @@ +--- +name: Release + +on: + release: + types: [published] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + vars: + name: Set Variables + outputs: + release-name: ${{ steps.release-name.outputs.release-name }} + runs-on: ubuntu-22.04 + timeout-minutes: 1 + steps: + - name: Release Name + id: release-name + run: | + echo release-name=$(curl https://api.github.com/repos/bcgov/onroutebc/releases/latest | jq -r .tag_name) >> $GITHUB_OUTPUT + + deploys-uat: + name: Deploys (Uat) + needs: [vars] + uses: ./.github/workflows/uat.yml + secrets: inherit + with: + tag: ${{ needs.vars.outputs.release-name }} + + deploys-prod: + name: Deploys (Prod) + needs: [deploys-uat, vars] + uses: ./.github/workflows/prod.yml + secrets: inherit + with: + tag: ${{ needs.vars.outputs.release-name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..c9e5732fb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +--- +name: Deploy Test + +on: + workflow_dispatch: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string + default: "test" + workflow_call: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string + default: "test" + +jobs: + deploy-test: + name: Deploys (TEST) + uses: ./.github/workflows/deploy.yml + secrets: inherit + with: + autoscaling: true + environment: test + release: test + tag: ${{ inputs.tag }} + params: | + --set-string global.license='c28f0c' \ + --set-string global.zone='test' \ + --set-string global.vault.role='nonprod' \ + --set-string global.vault.zone='test' \ + --set-string global.pr_num='${{ inputs.tag }}' \ diff --git a/.github/workflows/uat.yml b/.github/workflows/uat.yml index 7f4124713..84c7e8973 100644 --- a/.github/workflows/uat.yml +++ b/.github/workflows/uat.yml @@ -1,44 +1,35 @@ +--- name: Deploy UAT on: workflow_dispatch: inputs: - environment: - description: "Deployment environment - test --> vault secrets" - required: true - type: choice - options: ["test","tools"] + tag: + description: "Image tag to deploy" + required: false + type: string + default: "test" + workflow_call: + inputs: + tag: + description: "Image tag to deploy" + required: false + type: string default: "test" - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true - jobs: - uninstall-uat: - name: Uninstall (uat) - environment: ${{inputs.environment}} - runs-on: ubuntu-22.04 - steps: - - name: uninstall - run: | - oc login --token=${{ secrets.oc_token }} --server=${{ secrets.oc_server }} - oc project ${{ secrets.OC_NAMESPACE }} # Safeguard! - helm uninstall onroutebc-uat || true deploy-uat: name: Deploys (UAT) uses: ./.github/workflows/deploy.yml - needs: uninstall-uat secrets: inherit with: autoscaling: true - environment: ${{inputs.environment}} - tag: "test" - release: "uat" + environment: uat + tag: ${{inputs.tag}} + release: uat params: | --set-string global.license='c28f0c' \ --set-string global.zone='test' \ --set-string global.vault.role='nonprod' \ --set-string global.vault.zone='uat' \ - --set-string global.pr_num='${{ needs.vars.outputs.pr }}' \ + --set-string global.pr_num='${{ inputs.tag }}' \ diff --git a/.gitignore b/.gitignore index 3d005313e..60755bffc 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,4 @@ Chart.lock #Loadtest Results loadtests/results +loadtests/*/results From 1cbac3590e3550144e7adaadd4566139b7b323da Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:47:32 -0700 Subject: [PATCH 14/58] fix: moved release-please to its own file (#1608) --- .github/workflows/merge.yml | 12 ------ .github/workflows/release-please.yml | 29 ++++++++++++++ .github/workflows/release.yml | 57 +++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/release-please.yml diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 611023eb3..01c9f8fd1 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -63,15 +63,3 @@ jobs: repository: ${{ github.repository }}/${{ matrix.package }} target: ${{ needs.vars.outputs.pr }} tags: test #Promote images AFTER successful deploy - - release-please: - name: Release-Please - needs: [promote-images-test, vars] - runs-on: ubuntu-22.04 - timeout-minutes: 2 - steps: - - uses: google-github-actions/release-please-action@v3 - with: - release-type: simple - package-name: release-please-action - changelog-types: '[{"type":"ORV2","section":"Features","hidden":false},{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"build","section":"Miscellaneous","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"ci","section":"Miscellaneous","hidden":false},{"type":"docs","section":"Miscellaneous","hidden":false},{"type":"perf","section":"Miscellaneous","hidden":false},{"type":"refactor","section":"Miscellaneous","hidden":false},{"type":"revert","section":"Miscellaneous","hidden":false},{"type":"style","section":"Miscellaneous","hidden":false},{"type":"test","section":"Miscellaneous","hidden":false}]' diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 000000000..ae1a0b28a --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,29 @@ +--- +on: + push: + branches: [main] + paths-ignore: + - '*.md' + - '.github/**' + - 'common/graphics/**' + - '!.github/workflows/deploy.yml' + - '!.github/workflows/merge.yml' + +permissions: + contents: write + pull-requests: write + +name: release-please + +jobs: + vars: + release-please: + name: Release-Please + runs-on: ubuntu-22.04 + timeout-minutes: 2 + steps: + - uses: google-github-actions/release-please-action@v3 + with: + release-type: simple + package-name: release-please-action + changelog-types: '[{"type":"ORV2","section":"Features","hidden":false},{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"build","section":"Miscellaneous","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"ci","section":"Miscellaneous","hidden":false},{"type":"docs","section":"Miscellaneous","hidden":false},{"type":"perf","section":"Miscellaneous","hidden":false},{"type":"refactor","section":"Miscellaneous","hidden":false},{"type":"revert","section":"Miscellaneous","hidden":false},{"type":"style","section":"Miscellaneous","hidden":false},{"type":"test","section":"Miscellaneous","hidden":false}]' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01b4dd1b2..d87fce2cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,19 +22,72 @@ jobs: id: release-name run: | echo release-name=$(curl https://api.github.com/repos/bcgov/onroutebc/releases/latest | jq -r .tag_name) >> $GITHUB_OUTPUT + promote-images-release: + name: Promote Images - Release + needs: [vars] + runs-on: ubuntu-22.04 + permissions: + packages: write + strategy: + matrix: + package: [dops, vehicles, frontend, scheduler, policy] + timeout-minutes: 2 + steps: + - uses: shrink/actions-docker-registry-tag@v4 + with: + registry: ghcr.io + repository: ${{ github.repository }}/${{ matrix.package }} + target: test + tags: ${{ needs.vars.outputs.release-name }} deploys-uat: name: Deploys (Uat) - needs: [vars] + needs: [vars,promote-images-release] uses: ./.github/workflows/uat.yml secrets: inherit with: tag: ${{ needs.vars.outputs.release-name }} + promote-images-uat: + name: Promote Images - Uat + needs: [vars,deploys-uat] + runs-on: ubuntu-22.04 + permissions: + packages: write + strategy: + matrix: + package: [dops, vehicles, frontend, scheduler, policy] + timeout-minutes: 2 + steps: + - uses: shrink/actions-docker-registry-tag@v4 + with: + registry: ghcr.io + repository: ${{ github.repository }}/${{ matrix.package }} + target: ${{ needs.vars.outputs.release-name }} + tags: uat + deploys-prod: name: Deploys (Prod) - needs: [deploys-uat, vars] + needs: [vars,promote-images-uat] uses: ./.github/workflows/prod.yml secrets: inherit with: tag: ${{ needs.vars.outputs.release-name }} + + promote-images-prod: + name: Promote Images - Prod + needs: [vars,deploys-prod] + runs-on: ubuntu-22.04 + permissions: + packages: write + strategy: + matrix: + package: [dops, vehicles, frontend, scheduler, policy] + timeout-minutes: 2 + steps: + - uses: shrink/actions-docker-registry-tag@v4 + with: + registry: ghcr.io + repository: ${{ github.repository }}/${{ matrix.package }} + target: uat + tags: prod From a846d538e9666bb95b6e47324e3887fe1a668897 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:54:15 -0700 Subject: [PATCH 15/58] fix: removed vars from release-please.yml (#1609) --- .github/workflows/release-please.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ae1a0b28a..693e8dcb4 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -16,7 +16,6 @@ permissions: name: release-please jobs: - vars: release-please: name: Release-Please runs-on: ubuntu-22.04 From 854d6e3d258c04d0faa738dfe03b1f3a0873bb67 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:06:30 -0700 Subject: [PATCH 16/58] Fix/release please 3 (#1610) --- .github/workflows/release-please.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 693e8dcb4..ee63f3966 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -1,19 +1,14 @@ --- +name: release-please + on: push: branches: [main] - paths-ignore: - - '*.md' - - '.github/**' - - 'common/graphics/**' - - '!.github/workflows/deploy.yml' - - '!.github/workflows/merge.yml' permissions: contents: write pull-requests: write -name: release-please jobs: release-please: From 5986a0e424c0de64266ce0442a7620db61203bf0 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:25:05 -0700 Subject: [PATCH 17/58] feat: removed release-please (#1613) --- .github/workflows/release-please.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/release-please.yml diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml deleted file mode 100644 index ee63f3966..000000000 --- a/.github/workflows/release-please.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: release-please - -on: - push: - branches: [main] - -permissions: - contents: write - pull-requests: write - - -jobs: - release-please: - name: Release-Please - runs-on: ubuntu-22.04 - timeout-minutes: 2 - steps: - - uses: google-github-actions/release-please-action@v3 - with: - release-type: simple - package-name: release-please-action - changelog-types: '[{"type":"ORV2","section":"Features","hidden":false},{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"build","section":"Miscellaneous","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"ci","section":"Miscellaneous","hidden":false},{"type":"docs","section":"Miscellaneous","hidden":false},{"type":"perf","section":"Miscellaneous","hidden":false},{"type":"refactor","section":"Miscellaneous","hidden":false},{"type":"revert","section":"Miscellaneous","hidden":false},{"type":"style","section":"Miscellaneous","hidden":false},{"type":"test","section":"Miscellaneous","hidden":false}]' From 3fc25ed181db6910bca21536dcd1cd5d9c18ae38 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:44:26 -0400 Subject: [PATCH 18/58] ORV2-2693 Add STOS permit templates to OnRouteBC (#1612) --- .../versions/revert/v_43_ddl_revert.sql | 31 ++++++++++ database/mssql/scripts/versions/v_43_ddl.sql | 60 +++++++++++++++++++ database/mssql/test/versions/v_43_1_test.sql | 5 ++ database/mssql/test/versions/v_43_2_test.sql | 6 ++ database/mssql/test/versions/v_43_test.sh | 24 ++++++++ dops/src/enum/template-name.enum.ts | 3 + .../src/common/enum/template-name.enum.ts | 12 ++++ vehicles/src/common/helper/template.helper.ts | 45 ++++++++++++++ .../interface/permit.template.interface.ts | 7 ++- .../permit-receipt-document.service.ts | 20 ++----- 10 files changed, 197 insertions(+), 16 deletions(-) create mode 100644 database/mssql/scripts/versions/revert/v_43_ddl_revert.sql create mode 100644 database/mssql/scripts/versions/v_43_ddl.sql create mode 100644 database/mssql/test/versions/v_43_1_test.sql create mode 100644 database/mssql/test/versions/v_43_2_test.sql create mode 100644 database/mssql/test/versions/v_43_test.sh create mode 100644 vehicles/src/common/helper/template.helper.ts diff --git a/database/mssql/scripts/versions/revert/v_43_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_43_ddl_revert.sql new file mode 100644 index 000000000..7b9e1821d --- /dev/null +++ b/database/mssql/scripts/versions/revert/v_43_ddl_revert.sql @@ -0,0 +1,31 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON + +BEGIN TRY + BEGIN TRANSACTION + + DELETE [dops].[ORBC_DOCUMENT_TEMPLATE] WHERE DOCUMENT_ID IN (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-template-v1.docx') + DELETE [dops].[ORBC_DOCUMENT_TEMPLATE] WHERE DOCUMENT_ID IN (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-void-template-v1.docx') + DELETE [dops].[ORBC_DOCUMENT_TEMPLATE] WHERE DOCUMENT_ID IN (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-revoked-template-v1.docx') + DELETE [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-template-v1.docx' + DELETE [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-void-template-v1.docx' + DELETE [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-revoked-template-v1.docx' + COMMIT +END TRY + +BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK; + THROW +END CATCH + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Revert STOS templates' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (42, @VersionDescription, getutcdate()) diff --git a/database/mssql/scripts/versions/v_43_ddl.sql b/database/mssql/scripts/versions/v_43_ddl.sql new file mode 100644 index 000000000..c2a32b2a8 --- /dev/null +++ b/database/mssql/scripts/versions/v_43_ddl.sql @@ -0,0 +1,60 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT] ( [S3_OBJECT_ID], [S3_VERSION_ID], [S3_LOCATION], [OBJECT_MIME_TYPE], [FILE_NAME], [DMS_VERSION_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'273C4D93-544E-471B-ACB6-D79E51BE78DA', NULL, N'https://moti-int.objectstore.gov.bc.ca/tran_api_orbc_docs_dev/tran_api_orbc_docs_dev%40moti-int.objectstore.gov.bc.ca/273c4d93-544e-471b-acb6-d79e51be78da', N'application/vnd.openxmlformats-officedocument.wordprocessingml.document',N'stos-template-v1.docx',1, 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT] ( [S3_OBJECT_ID], [S3_VERSION_ID], [S3_LOCATION], [OBJECT_MIME_TYPE], [FILE_NAME], [DMS_VERSION_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'E28B476E-361E-45A8-A9A5-384C7F4CB8D8', NULL, N'https://moti-int.objectstore.gov.bc.ca/tran_api_orbc_docs_dev/tran_api_orbc_docs_dev%40moti-int.objectstore.gov.bc.ca/e28b476e-361e-45a8-a9a5-384c7f4cb8d8', N'application/vnd.openxmlformats-officedocument.wordprocessingml.document',N'stos-void-template-v1.docx',1, 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT] ( [S3_OBJECT_ID], [S3_VERSION_ID], [S3_LOCATION], [OBJECT_MIME_TYPE], [FILE_NAME], [DMS_VERSION_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'F5BAD4DC-A1AE-4ABF-98DC-17874871F882', NULL, N'https://moti-int.objectstore.gov.bc.ca/tran_api_orbc_docs_dev/tran_api_orbc_docs_dev%40moti-int.objectstore.gov.bc.ca/f5bad4dc-a1ae-4abf-98dc-17874871f882', N'application/vnd.openxmlformats-officedocument.wordprocessingml.document',N'stos-revoked-template-v1.docx',1, 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT_TEMPLATE] ( [TEMPLATE_NAME], [TEMPLATE_VERSION], [DOCUMENT_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'PERMIT_STOS', 1, (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-template-v1.docx'), 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT_TEMPLATE] ( [TEMPLATE_NAME], [TEMPLATE_VERSION], [DOCUMENT_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'PERMIT_STOS_VOID', 1, (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-void-template-v1.docx'), 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT_TEMPLATE] ( [TEMPLATE_NAME], [TEMPLATE_VERSION], [DOCUMENT_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'PERMIT_STOS_REVOKED', 1, (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='stos-revoked-template-v1.docx'), 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'STOS permit templates.' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (43, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO + diff --git a/database/mssql/test/versions/v_43_1_test.sql b/database/mssql/test/versions/v_43_1_test.sql new file mode 100644 index 000000000..92807f1ed --- /dev/null +++ b/database/mssql/test/versions/v_43_1_test.sql @@ -0,0 +1,5 @@ +-- Test that the permit template have been configured correctly +SET NOCOUNT ON + +SELECT COUNT(*) FROM $(DB_NAME).[dops].[ORBC_DOCUMENT_TEMPLATE] +WHERE TEMPLATE_NAME IN ('PERMIT','PERMIT_STOS_VOID','PERMIT_STOS_REVOKED') \ No newline at end of file diff --git a/database/mssql/test/versions/v_43_2_test.sql b/database/mssql/test/versions/v_43_2_test.sql new file mode 100644 index 000000000..a31775ab7 --- /dev/null +++ b/database/mssql/test/versions/v_43_2_test.sql @@ -0,0 +1,6 @@ +-- Test that the permit template have been configured correctly +SET NOCOUNT ON + +SELECT COUNT(*) FROM $(DB_NAME).[dops].[ORBC_DOCUMENT] +WHERE ID IN (SELECT DOCUMENT_ID FROM $(DB_NAME).[dops].[ORBC_DOCUMENT_TEMPLATE] +WHERE TEMPLATE_NAME IN ('PERMIT','PERMIT_STOS_VOID','PERMIT_STOS_REVOKED')) \ No newline at end of file diff --git a/database/mssql/test/versions/v_43_test.sh b/database/mssql/test/versions/v_43_test.sh new file mode 100644 index 000000000..867f594cb --- /dev/null +++ b/database/mssql/test/versions/v_43_test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Retrieve arguments +source ${SCRIPT_DIR}/utility/getopt.sh +USAGE="-u USER -p PASS -s SERVER -d DATABASE" +parse_options "${USAGE}" ${@} + +# All database tests for database version 43 are run from this shell script. +# TESTS_DIR variable set by the calling test-runner script. + +TEST_43_1_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_43_1_test.sql | xargs) +if [[ $TEST_43_1_RESULT -eq 3 ]]; then + echo "Test 43.1 passed: STOS templates setup successfully in ORBC_DOCUMENT_TEMPLATE" +else + echo "******** Test 43.1 failed: Failed to setup STOS permit templates" +fi + + +TEST_43_2_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_43_2_test.sql | xargs) +if [[ $TEST_43_2_RESULT -eq 3 ]]; then + echo "Test 43.2 passed: STOS templates setup successfully in ORBC_DOCUMENT" +else + echo "******** Test 43.2 failed: Failed to setup STOS permit templates" +fi \ No newline at end of file diff --git a/dops/src/enum/template-name.enum.ts b/dops/src/enum/template-name.enum.ts index dbab6861b..d04f110f2 100644 --- a/dops/src/enum/template-name.enum.ts +++ b/dops/src/enum/template-name.enum.ts @@ -3,4 +3,7 @@ export enum TemplateName { PAYMENT_RECEIPT = 'PAYMENT_RECEIPT', PERMIT_VOID = 'PERMIT_VOID', PERMIT_REVOKED = 'PERMIT_REVOKED', + PERMIT_STOS = 'PERMIT_STOS', + PERMIT_STOS_VOID = 'PERMIT_STOS_VOID', + PERMIT_STOS_REVOKED = 'PERMIT_STOS_REVOKED', } diff --git a/vehicles/src/common/enum/template-name.enum.ts b/vehicles/src/common/enum/template-name.enum.ts index dbab6861b..d29aa5e0c 100644 --- a/vehicles/src/common/enum/template-name.enum.ts +++ b/vehicles/src/common/enum/template-name.enum.ts @@ -1,6 +1,18 @@ +import { ApplicationStatus } from './application-status.enum'; +import { PermitType } from './permit-type.enum'; + export enum TemplateName { PERMIT = 'PERMIT', PAYMENT_RECEIPT = 'PAYMENT_RECEIPT', PERMIT_VOID = 'PERMIT_VOID', PERMIT_REVOKED = 'PERMIT_REVOKED', + PERMIT_STOS = 'PERMIT_STOS', + PERMIT_STOS_VOID = 'PERMIT_STOS_VOID', + PERMIT_STOS_REVOKED = 'PERMIT_STOS_REVOKED', } + +export type PermitTemplateMapping = { + [K in ApplicationStatus]?: { + [T in PermitType | 'default']?: TemplateName; + }; +}; diff --git a/vehicles/src/common/helper/template.helper.ts b/vehicles/src/common/helper/template.helper.ts new file mode 100644 index 000000000..60fa26d83 --- /dev/null +++ b/vehicles/src/common/helper/template.helper.ts @@ -0,0 +1,45 @@ +import { InternalServerErrorException } from '@nestjs/common'; +import { ApplicationStatus } from '../enum/application-status.enum'; +import { PermitType } from '../enum/permit-type.enum'; +import { + PermitTemplateMapping, + TemplateName, +} from '../enum/template-name.enum'; + +/** + * Returns the appropriate template name based on the provided application status and permit type. + * Throws an error if the combination of status and type does not yield a valid template. + * + * @param {ApplicationStatus} status - The status of the application for which the template is required. + * @param {PermitType} type - The type of permit for which the template is required. + * @returns {TemplateName} - The template name corresponding to the status and type provided. + * @throws {InternalServerErrorException} - If no valid template is found for the given status. + */ +export const getPermitTemplateName = ( + status: ApplicationStatus, + type: PermitType, +): TemplateName => { + const templateMapping: PermitTemplateMapping = { + [ApplicationStatus.ISSUED]: { + [PermitType.SINGLE_TRIP_OVERSIZE]: TemplateName.PERMIT_STOS, + default: TemplateName.PERMIT, + }, + [ApplicationStatus.VOIDED]: { + [PermitType.SINGLE_TRIP_OVERSIZE]: TemplateName.PERMIT_STOS_VOID, + default: TemplateName.PERMIT_VOID, + }, + [ApplicationStatus.REVOKED]: { + [PermitType.SINGLE_TRIP_OVERSIZE]: TemplateName.PERMIT_STOS_REVOKED, + default: TemplateName.PERMIT_REVOKED, + }, + }; + + const template = + templateMapping[status]?.[type] || templateMapping[status]?.default; + if (!template) { + throw new InternalServerErrorException( + 'Invalid status for document generation', + ); + } + return template; +}; diff --git a/vehicles/src/common/interface/permit.template.interface.ts b/vehicles/src/common/interface/permit.template.interface.ts index ccc551f80..8ac315da7 100644 --- a/vehicles/src/common/interface/permit.template.interface.ts +++ b/vehicles/src/common/interface/permit.template.interface.ts @@ -33,7 +33,7 @@ export interface PermitData { clientNumber: string; vehicleConfiguration?: VehicleConfiguration; applicationNotes?: string; - permittedCommodity?: string; + permittedCommodity?: PermittedCommodity; permittedRoute?: PermittedRoute; } @@ -51,6 +51,11 @@ interface PermittedRoute { manualRoute: ManualRoute; } +interface PermittedCommodity { + commodityType: string; + loadDescription: string; +} + interface ManualRoute { origin: string; destination: string; diff --git a/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts b/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts index 68396c76b..7e4d2d550 100644 --- a/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts +++ b/vehicles/src/modules/permit-application-payment/permit-receipt-document/permit-receipt-document.service.ts @@ -39,6 +39,7 @@ import { generateFaxEmail, validateEmailandFaxList, } from '../../../common/helper/notification.helper'; +import { getPermitTemplateName } from '../../../common/helper/template.helper'; @Injectable() export class PermitReceiptDocumentService { @@ -275,21 +276,10 @@ export class PermitReceiptDocumentService { ); const dopsRequestData = { - templateName: (() => { - switch (fetchedPermit.permitStatus) { - case ApplicationStatus.ISSUED: - return TemplateName.PERMIT; - case ApplicationStatus.VOIDED: - return TemplateName.PERMIT_VOID; - case ApplicationStatus.REVOKED: - return TemplateName.PERMIT_REVOKED; - default: - // Handle the default case here, for example: - throw new InternalServerErrorException( - 'Invalid status for document generation', - ); - } - })(), + templateName: getPermitTemplateName( + fetchedPermit?.permitStatus, + fetchedPermit?.permitType, + ), generatedDocumentFileName: permitDataForTemplate.permitNumber, templateData: permitDataForTemplate, documentsToMerge: permitDataForTemplate.permitData.commodities.map( From 5d58f53fb448f76fdf6b691dac2146bbdc399dc0 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:12:04 -0700 Subject: [PATCH 19/58] feat: removed release-please (#1614) From 62d6e897771e27899d2febfa48417b741d3f66c7 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:25:12 -0700 Subject: [PATCH 20/58] feat: Release events now should continue pipeline via pr (#1615) --- .github/workflows/release.yml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d87fce2cf..2c7368475 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,11 @@ on: release: types: [published] workflow_dispatch: + inputs: + pr: + description: "PR num of image to deploy" + required: false + type: string concurrency: group: ${{ github.workflow }} @@ -14,14 +19,23 @@ jobs: vars: name: Set Variables outputs: - release-name: ${{ steps.release-name.outputs.release-name }} + pr: ${{ steps.pr.outputs.pr }} runs-on: ubuntu-22.04 timeout-minutes: 1 steps: + # Get PR number for squash merges to main + - name: Get PR Number From Event + if: ${{ github.event_name == 'release' }} + id: pr_no + uses: bcgov-nr/action-get-pr@v0.0.1 + - name: PR Number + id: pr + run: echo pr=${{ steps.pr_no.outputs.pr || inputs.pr}} >> $GITHUB_OUTPUT - name: Release Name id: release-name run: | echo release-name=$(curl https://api.github.com/repos/bcgov/onroutebc/releases/latest | jq -r .tag_name) >> $GITHUB_OUTPUT + promote-images-release: name: Promote Images - Release needs: [vars] @@ -37,7 +51,7 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: test + target: ${{ needs.vars.outputs.pr }} tags: ${{ needs.vars.outputs.release-name }} deploys-uat: @@ -46,7 +60,7 @@ jobs: uses: ./.github/workflows/uat.yml secrets: inherit with: - tag: ${{ needs.vars.outputs.release-name }} + tag: ${{ needs.vars.outputs.pr }} promote-images-uat: name: Promote Images - Uat @@ -63,7 +77,7 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: ${{ needs.vars.outputs.release-name }} + target: ${{ needs.vars.outputs.pr }} tags: uat deploys-prod: @@ -72,7 +86,7 @@ jobs: uses: ./.github/workflows/prod.yml secrets: inherit with: - tag: ${{ needs.vars.outputs.release-name }} + tag: ${{ needs.vars.outputs.pr }} promote-images-prod: name: Promote Images - Prod @@ -89,5 +103,5 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: uat + target: ${{ needs.vars.outputs.pr }} tags: prod From 1bb6ece68bf86a2c9f506350c5542b92e78386a5 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:49:36 -0700 Subject: [PATCH 21/58] Fix/release event add pr (#1616) --- .github/workflows/merge.yml | 3 --- .github/workflows/release.yml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 01c9f8fd1..97ce93ebb 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -5,10 +5,7 @@ on: branches: [main] paths-ignore: - '*.md' - - '.github/**' - 'common/graphics/**' - - '!.github/workflows/deploy.yml' - - '!.github/workflows/merge.yml' workflow_dispatch: inputs: pr: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c7368475..90c3f905f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Get PR Number From Event if: ${{ github.event_name == 'release' }} id: pr_no - uses: bcgov-nr/action-get-pr@v0.0.1 + uses: bcgov-nr/action-get-pr@main - name: PR Number id: pr run: echo pr=${{ steps.pr_no.outputs.pr || inputs.pr}} >> $GITHUB_OUTPUT From af4046a72e554cbb70be64f005229dc97176954c Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:23:42 -0400 Subject: [PATCH 22/58] ORV2-2693 Add License GVW to permitData (#1618) --- vehicles/src/common/interface/permit.template.interface.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/vehicles/src/common/interface/permit.template.interface.ts b/vehicles/src/common/interface/permit.template.interface.ts index 8ac315da7..baf7db1f7 100644 --- a/vehicles/src/common/interface/permit.template.interface.ts +++ b/vehicles/src/common/interface/permit.template.interface.ts @@ -92,6 +92,7 @@ interface VehicleDetails { provinceCode: string; vehicleType: string; vehicleSubType: string; + licensedGVW?: number; saveVehicle?: boolean; } From 7a637035dc644f132e29f42ac9ae6d2cb253f1ea Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:28:26 -0700 Subject: [PATCH 23/58] fix: removing release events due to pr data not in event (#1617) --- .github/workflows/release.yml | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 90c3f905f..1665ec400 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,11 +5,6 @@ on: release: types: [published] workflow_dispatch: - inputs: - pr: - description: "PR num of image to deploy" - required: false - type: string concurrency: group: ${{ github.workflow }} @@ -19,18 +14,10 @@ jobs: vars: name: Set Variables outputs: - pr: ${{ steps.pr.outputs.pr }} + release-name: ${{ steps.release-name.outputs.release-name }} runs-on: ubuntu-22.04 timeout-minutes: 1 steps: - # Get PR number for squash merges to main - - name: Get PR Number From Event - if: ${{ github.event_name == 'release' }} - id: pr_no - uses: bcgov-nr/action-get-pr@main - - name: PR Number - id: pr - run: echo pr=${{ steps.pr_no.outputs.pr || inputs.pr}} >> $GITHUB_OUTPUT - name: Release Name id: release-name run: | @@ -51,7 +38,7 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: ${{ needs.vars.outputs.pr }} + target: test tags: ${{ needs.vars.outputs.release-name }} deploys-uat: @@ -60,7 +47,7 @@ jobs: uses: ./.github/workflows/uat.yml secrets: inherit with: - tag: ${{ needs.vars.outputs.pr }} + tag: ${{ needs.vars.outputs.release-name }} promote-images-uat: name: Promote Images - Uat @@ -77,7 +64,7 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: ${{ needs.vars.outputs.pr }} + target: ${{ needs.vars.outputs.release-name }} tags: uat deploys-prod: @@ -86,7 +73,7 @@ jobs: uses: ./.github/workflows/prod.yml secrets: inherit with: - tag: ${{ needs.vars.outputs.pr }} + tag: ${{ needs.vars.outputs.release-name }} promote-images-prod: name: Promote Images - Prod @@ -103,5 +90,5 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: ${{ needs.vars.outputs.pr }} + target: ${{ needs.vars.outputs.release-name }} tags: prod From 945a352d37391cc46e481a3fff5880b948acb96a Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:39:20 -0400 Subject: [PATCH 24/58] feature: ORV2-2841 MFP Permit Template and BE changes (#1619) --- .../versions/revert/v_44_ddl_revert.sql | 31 ++++++++++ database/mssql/scripts/versions/v_44_ddl.sql | 60 +++++++++++++++++++ database/mssql/test/versions/v_44_1_test.sql | 5 ++ database/mssql/test/versions/v_44_2_test.sql | 6 ++ database/mssql/test/versions/v_44_test.sh | 24 ++++++++ dops/src/enum/template-name.enum.ts | 3 + .../src/common/enum/template-name.enum.ts | 3 + vehicles/src/common/helper/template.helper.ts | 3 + .../interface/permit.template.interface.ts | 4 +- 9 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 database/mssql/scripts/versions/revert/v_44_ddl_revert.sql create mode 100644 database/mssql/scripts/versions/v_44_ddl.sql create mode 100644 database/mssql/test/versions/v_44_1_test.sql create mode 100644 database/mssql/test/versions/v_44_2_test.sql create mode 100644 database/mssql/test/versions/v_44_test.sh diff --git a/database/mssql/scripts/versions/revert/v_44_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_44_ddl_revert.sql new file mode 100644 index 000000000..6e9114396 --- /dev/null +++ b/database/mssql/scripts/versions/revert/v_44_ddl_revert.sql @@ -0,0 +1,31 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON + +BEGIN TRY + BEGIN TRANSACTION + + DELETE [dops].[ORBC_DOCUMENT_TEMPLATE] WHERE DOCUMENT_ID IN (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-template-v1.docx') + DELETE [dops].[ORBC_DOCUMENT_TEMPLATE] WHERE DOCUMENT_ID IN (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-void-template-v1.docx') + DELETE [dops].[ORBC_DOCUMENT_TEMPLATE] WHERE DOCUMENT_ID IN (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-revoked-template-v1.docx') + DELETE [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-template-v1.docx' + DELETE [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-void-template-v1.docx' + DELETE [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-revoked-template-v1.docx' + COMMIT +END TRY + +BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK; + THROW +END CATCH + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Revert MFP templates' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (42, @VersionDescription, getutcdate()) diff --git a/database/mssql/scripts/versions/v_44_ddl.sql b/database/mssql/scripts/versions/v_44_ddl.sql new file mode 100644 index 000000000..039a877a3 --- /dev/null +++ b/database/mssql/scripts/versions/v_44_ddl.sql @@ -0,0 +1,60 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT] ( [S3_OBJECT_ID], [S3_VERSION_ID], [S3_LOCATION], [OBJECT_MIME_TYPE], [FILE_NAME], [DMS_VERSION_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'FF765BCC-7778-471C-9468-B788E358A07A', NULL, N'https://moti-int.objectstore.gov.bc.ca/tran_api_orbc_docs_dev/tran_api_orbc_docs_dev%40moti-int.objectstore.gov.bc.ca/ff765bcc-7778-471c-9468-b788e358a07a', N'application/vnd.openxmlformats-officedocument.wordprocessingml.document',N'mfp-template-v1.docx',1, 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT] ( [S3_OBJECT_ID], [S3_VERSION_ID], [S3_LOCATION], [OBJECT_MIME_TYPE], [FILE_NAME], [DMS_VERSION_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'FF765BCC-7778-471C-9468-B788E358A07A', NULL, N'https://moti-int.objectstore.gov.bc.ca/tran_api_orbc_docs_dev/tran_api_orbc_docs_dev%40moti-int.objectstore.gov.bc.ca/ff765bcc-7778-471c-9468-b788e358a07a', N'application/vnd.openxmlformats-officedocument.wordprocessingml.document',N'mfp-void-template-v1.docx',1, 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT] ( [S3_OBJECT_ID], [S3_VERSION_ID], [S3_LOCATION], [OBJECT_MIME_TYPE], [FILE_NAME], [DMS_VERSION_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'BCF467A3-23D0-4E1F-8CA1-EC47656F483E', NULL, N'https://moti-int.objectstore.gov.bc.ca/tran_api_orbc_docs_dev/tran_api_orbc_docs_dev%40moti-int.objectstore.gov.bc.ca/bcf467a3-23d0-4e1f-8ca1-ec47656f483e', N'application/vnd.openxmlformats-officedocument.wordprocessingml.document',N'mfp-revoked-template-v1.docx',1, 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT_TEMPLATE] ( [TEMPLATE_NAME], [TEMPLATE_VERSION], [DOCUMENT_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'PERMIT_MFP', 1, (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-template-v1.docx'), 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT_TEMPLATE] ( [TEMPLATE_NAME], [TEMPLATE_VERSION], [DOCUMENT_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'PERMIT_MFP_VOID', 1, (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-void-template-v1.docx'), 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) +IF @@ERROR <> 0 SET NOEXEC ON +GO +INSERT [dops].[ORBC_DOCUMENT_TEMPLATE] ( [TEMPLATE_NAME], [TEMPLATE_VERSION], [DOCUMENT_ID], [CONCURRENCY_CONTROL_NUMBER], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES ( N'PERMIT_MFP_REVOKED', 1, (SELECT ID FROM [dops].[ORBC_DOCUMENT] WHERE FILE_NAME='mfp-revoked-template-v1.docx'), 1, N'dops', GETUTCDATE(), N'dops', GETUTCDATE()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'MFP permit templates.' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (43, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO + diff --git a/database/mssql/test/versions/v_44_1_test.sql b/database/mssql/test/versions/v_44_1_test.sql new file mode 100644 index 000000000..1f3ab7ede --- /dev/null +++ b/database/mssql/test/versions/v_44_1_test.sql @@ -0,0 +1,5 @@ +-- Test that the permit template have been configured correctly +SET NOCOUNT ON + +SELECT COUNT(*) FROM $(DB_NAME).[dops].[ORBC_DOCUMENT_TEMPLATE] +WHERE TEMPLATE_NAME IN ('PERMIT','PERMIT_MFP_VOID','PERMIT_MFP_REVOKED') \ No newline at end of file diff --git a/database/mssql/test/versions/v_44_2_test.sql b/database/mssql/test/versions/v_44_2_test.sql new file mode 100644 index 000000000..5d7b4f763 --- /dev/null +++ b/database/mssql/test/versions/v_44_2_test.sql @@ -0,0 +1,6 @@ +-- Test that the permit template have been configured correctly +SET NOCOUNT ON + +SELECT COUNT(*) FROM $(DB_NAME).[dops].[ORBC_DOCUMENT] +WHERE ID IN (SELECT DOCUMENT_ID FROM $(DB_NAME).[dops].[ORBC_DOCUMENT_TEMPLATE] +WHERE TEMPLATE_NAME IN ('PERMIT','PERMIT_MFP_VOID','PERMIT_MFP_REVOKED')) \ No newline at end of file diff --git a/database/mssql/test/versions/v_44_test.sh b/database/mssql/test/versions/v_44_test.sh new file mode 100644 index 000000000..3a42c1246 --- /dev/null +++ b/database/mssql/test/versions/v_44_test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Retrieve arguments +source ${SCRIPT_DIR}/utility/getopt.sh +USAGE="-u USER -p PASS -s SERVER -d DATABASE" +parse_options "${USAGE}" ${@} + +# All database tests for database version 44 are run from this shell script. +# TESTS_DIR variable set by the calling test-runner script. + +TEST_44_1_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_44_1_test.sql | xargs) +if [[ $TEST_44_1_RESULT -eq 3 ]]; then + echo "Test 44.1 passed: MFP templates setup successfully in ORBC_DOCUMENT_TEMPLATE" +else + echo "******** Test 44.1 failed: Failed to setup MFP permit templates" +fi + + +TEST_44_2_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_44_2_test.sql | xargs) +if [[ $TEST_44_2_RESULT -eq 3 ]]; then + echo "Test 44.2 passed: MFP templates setup successfully in ORBC_DOCUMENT" +else + echo "******** Test 44.2 failed: Failed to setup MFP permit templates" +fi \ No newline at end of file diff --git a/dops/src/enum/template-name.enum.ts b/dops/src/enum/template-name.enum.ts index d04f110f2..5c3fb2179 100644 --- a/dops/src/enum/template-name.enum.ts +++ b/dops/src/enum/template-name.enum.ts @@ -6,4 +6,7 @@ export enum TemplateName { PERMIT_STOS = 'PERMIT_STOS', PERMIT_STOS_VOID = 'PERMIT_STOS_VOID', PERMIT_STOS_REVOKED = 'PERMIT_STOS_REVOKED', + PERMIT_MFP = 'PERMIT_MFP', + PERMIT_MFP_VOID = 'PERMIT_MFP_VOID', + PERMIT_MFP_REVOKED = 'PERMIT_MFP_REVOKED', } diff --git a/vehicles/src/common/enum/template-name.enum.ts b/vehicles/src/common/enum/template-name.enum.ts index d29aa5e0c..195274c85 100644 --- a/vehicles/src/common/enum/template-name.enum.ts +++ b/vehicles/src/common/enum/template-name.enum.ts @@ -9,6 +9,9 @@ export enum TemplateName { PERMIT_STOS = 'PERMIT_STOS', PERMIT_STOS_VOID = 'PERMIT_STOS_VOID', PERMIT_STOS_REVOKED = 'PERMIT_STOS_REVOKED', + PERMIT_MFP = 'PERMIT_MFP', + PERMIT_MFP_VOID = 'PERMIT_MFP_VOID', + PERMIT_MFP_REVOKED = 'PERMIT_MFP_REVOKED', } export type PermitTemplateMapping = { diff --git a/vehicles/src/common/helper/template.helper.ts b/vehicles/src/common/helper/template.helper.ts index 60fa26d83..22c29cf73 100644 --- a/vehicles/src/common/helper/template.helper.ts +++ b/vehicles/src/common/helper/template.helper.ts @@ -22,14 +22,17 @@ export const getPermitTemplateName = ( const templateMapping: PermitTemplateMapping = { [ApplicationStatus.ISSUED]: { [PermitType.SINGLE_TRIP_OVERSIZE]: TemplateName.PERMIT_STOS, + [PermitType.MOTIVE_FUEL_USER]: TemplateName.PERMIT_MFP, default: TemplateName.PERMIT, }, [ApplicationStatus.VOIDED]: { [PermitType.SINGLE_TRIP_OVERSIZE]: TemplateName.PERMIT_STOS_VOID, + [PermitType.MOTIVE_FUEL_USER]: TemplateName.PERMIT_MFP_VOID, default: TemplateName.PERMIT_VOID, }, [ApplicationStatus.REVOKED]: { [PermitType.SINGLE_TRIP_OVERSIZE]: TemplateName.PERMIT_STOS_REVOKED, + [PermitType.MOTIVE_FUEL_USER]: TemplateName.PERMIT_MFP_REVOKED, default: TemplateName.PERMIT_REVOKED, }, }; diff --git a/vehicles/src/common/interface/permit.template.interface.ts b/vehicles/src/common/interface/permit.template.interface.ts index baf7db1f7..80093aed4 100644 --- a/vehicles/src/common/interface/permit.template.interface.ts +++ b/vehicles/src/common/interface/permit.template.interface.ts @@ -59,7 +59,9 @@ interface PermittedCommodity { interface ManualRoute { origin: string; destination: string; - highwaySequence: string[]; + exitPoint?: string; + totalDistance?: number; + highwaySequence?: string[]; } interface MailingAddress { From b453ac48d11023b4ba4e02dd815f595d9b8b7d82 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:33:36 -0400 Subject: [PATCH 25/58] fix: ORV2-2841 Correct the DB version for 44 (#1620) --- database/mssql/scripts/versions/revert/v_44_ddl_revert.sql | 2 +- database/mssql/scripts/versions/v_44_ddl.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/database/mssql/scripts/versions/revert/v_44_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_44_ddl_revert.sql index 6e9114396..36ed4666e 100644 --- a/database/mssql/scripts/versions/revert/v_44_ddl_revert.sql +++ b/database/mssql/scripts/versions/revert/v_44_ddl_revert.sql @@ -28,4 +28,4 @@ END CATCH DECLARE @VersionDescription VARCHAR(255) SET @VersionDescription = 'Revert MFP templates' -INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (42, @VersionDescription, getutcdate()) +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (43, @VersionDescription, getutcdate()) diff --git a/database/mssql/scripts/versions/v_44_ddl.sql b/database/mssql/scripts/versions/v_44_ddl.sql index 039a877a3..2d176aaa1 100644 --- a/database/mssql/scripts/versions/v_44_ddl.sql +++ b/database/mssql/scripts/versions/v_44_ddl.sql @@ -40,7 +40,7 @@ GO DECLARE @VersionDescription VARCHAR(255) SET @VersionDescription = 'MFP permit templates.' -INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (43, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (44, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) IF @@ERROR <> 0 SET NOEXEC ON GO From 61ede2270b168b6aac66107502176ee2003b48ec Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:01:18 -0700 Subject: [PATCH 26/58] fix: deploying via last merged pr instead of tag promotion (#1621) --- .github/workflows/release.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1665ec400..a2e83b8bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,19 @@ jobs: vars: name: Set Variables outputs: + pr: ${{ steps.pr.outputs.pr }} release-name: ${{ steps.release-name.outputs.release-name }} runs-on: ubuntu-22.04 timeout-minutes: 1 steps: + # Get PR number for squash merges to main + - name: Get PR Number From Event + if: ${{ github.event_name == 'push' }} + id: pr_no + uses: bcgov-nr/action-get-pr@main + - name: PR Number + id: pr + run: echo pr=${{ steps.pr_no.outputs.pr || inputs.pr}} >> $GITHUB_OUTPUT - name: Release Name id: release-name run: | @@ -38,7 +47,7 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: test + target: ${{ needs.vars.outputs.pr }} tags: ${{ needs.vars.outputs.release-name }} deploys-uat: @@ -47,7 +56,7 @@ jobs: uses: ./.github/workflows/uat.yml secrets: inherit with: - tag: ${{ needs.vars.outputs.release-name }} + tag: ${{ needs.vars.outputs.pr }} promote-images-uat: name: Promote Images - Uat @@ -64,7 +73,7 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: ${{ needs.vars.outputs.release-name }} + target: ${{ needs.vars.outputs.pr }} tags: uat deploys-prod: @@ -73,7 +82,7 @@ jobs: uses: ./.github/workflows/prod.yml secrets: inherit with: - tag: ${{ needs.vars.outputs.release-name }} + tag: ${{ needs.vars.outputs.pr }} promote-images-prod: name: Promote Images - Prod @@ -90,5 +99,5 @@ jobs: with: registry: ghcr.io repository: ${{ github.repository }}/${{ matrix.package }} - target: ${{ needs.vars.outputs.release-name }} + target: ${{ needs.vars.outputs.pr }} tags: prod From 888f0d4a2be905776a3d40799968bd5bdd057087 Mon Sep 17 00:00:00 2001 From: cberg-aot <93226309+cberg-aot@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:21:04 -0700 Subject: [PATCH 27/58] fix: catching release event (#1622) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2e83b8bc..fbf2a8dae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: steps: # Get PR number for squash merges to main - name: Get PR Number From Event - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'release' }} id: pr_no uses: bcgov-nr/action-get-pr@main - name: PR Number From 5e14488dad19487637e7107281d0daa57d352b09 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:58:58 -0400 Subject: [PATCH 28/58] fix:ORV2-2792 Rectify the expiry date validation for Staff Queue (#1623) --- .../application/application.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vehicles/src/modules/permit-application-payment/application/application.service.ts b/vehicles/src/modules/permit-application-payment/application/application.service.ts index 0db543da1..4e9720a78 100644 --- a/vehicles/src/modules/permit-application-payment/application/application.service.ts +++ b/vehicles/src/modules/permit-application-payment/application/application.service.ts @@ -583,7 +583,7 @@ export class ApplicationService { existingApplication?.permitData?.permitData, ) as PermitData; const currentDate = dayjs(new Date().toISOString())?.format('YYYY-MM-DD'); - if (differenceBetween(permitData?.startDate, currentDate, 'days') < 0) { + if (differenceBetween(permitData?.startDate, currentDate, 'days') > 0) { throwUnprocessableEntityException('Start Date is in the past.'); } } @@ -1042,9 +1042,9 @@ export class ApplicationService { if ( application.permitStatus === ApplicationStatus.IN_QUEUE && - (differenceBetween(permitData?.startDate, currentDate, 'days') < + (differenceBetween(permitData?.startDate, currentDate, 'days') > 0 || - differenceBetween(permitData?.expiryDate, currentDate, 'days') < + differenceBetween(permitData?.expiryDate, currentDate, 'days') > 0) ) { throwUnprocessableEntityException( From 21fa1211be949a10b89a53f86635a641c68f8001 Mon Sep 17 00:00:00 2001 From: gchauhan-aot <113390759+gchauhan-aot@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:11:12 -0600 Subject: [PATCH 29/58] Permission Changes For LoA And Special Auth (#1624) --- .../modules/special-auth/loa.controller.ts | 48 ++++++++++++++++--- .../special-auth/special-auth.controller.ts | 25 ++++++++-- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/vehicles/src/modules/special-auth/loa.controller.ts b/vehicles/src/modules/special-auth/loa.controller.ts index 2f0c8e17e..1a63b1eda 100644 --- a/vehicles/src/modules/special-auth/loa.controller.ts +++ b/vehicles/src/modules/special-auth/loa.controller.ts @@ -40,10 +40,14 @@ import { LoaIdPathParamDto } from './dto/request/pathParam/loa-Id.path-params.dt import { GetDocumentQueryParamsDto } from '../common/dto/request/queryParam/getDocument.query-params.dto'; import { IsFeatureFlagEnabled } from '../../common/decorator/is-feature-flag-enabled.decorator'; import { Permissions } from 'src/common/decorator/permissions.decorator'; -import { Claim } from 'src/common/enum/claims.enum'; import { ReadLoaDto } from './dto/response/read-loa.dto'; import { GetLoaQueryParamsDto } from './dto/request/queryParam/get-loa.query-params.dto'; import { UpdateLoaFileDto } from './dto/request/update-loa-file.dto'; +import { + CLIENT_USER_ROLE_LIST, + IDIR_USER_ROLE_LIST, + IDIRUserRole, +} from 'src/common/enum/user-role.enum'; @ApiBearerAuth() @ApiTags('Letter of Authorization (LoA)') @@ -78,7 +82,12 @@ export class LoaController { type: ReadLoaDto, }) @ApiConsumes('multipart/form-data') - @Permissions({ claim: Claim.WRITE_LOA }) + @Permissions({ + allowedIdirRoles: [ + IDIRUserRole.HQ_ADMINISTRATOR, + IDIRUserRole.SYSTEM_ADMINISTRATOR, + ], + }) @Post() @UseInterceptors(FileInterceptor('file'), JsonReqBodyInterceptor) async create( @@ -109,6 +118,10 @@ export class LoaController { summary: 'Get all LoA for a company.', description: 'Returns all LOAs for a company in the database.', }) + @Permissions({ + allowedIdirRoles: IDIR_USER_ROLE_LIST, + allowedBCeIDRoles: CLIENT_USER_ROLE_LIST, + }) @Get() async get( @Param() { companyId }: CompanyIdPathParamDto, @@ -125,7 +138,10 @@ export class LoaController { summary: 'Get LoA by Id.', description: 'Returns the LoA object from the database.', }) - @Permissions({ claim: Claim.READ_LOA }) + @Permissions({ + allowedIdirRoles: IDIR_USER_ROLE_LIST, + allowedBCeIDRoles: CLIENT_USER_ROLE_LIST, + }) @Get('/:loaId') async getById( @Req() request: Request, @@ -141,7 +157,12 @@ export class LoaController { description: 'Updates and returns the LoA object from the database.', }) @ApiConsumes('multipart/form-data') - @Permissions({ claim: Claim.WRITE_LOA }) + @Permissions({ + allowedIdirRoles: [ + IDIRUserRole.HQ_ADMINISTRATOR, + IDIRUserRole.SYSTEM_ADMINISTRATOR, + ], + }) @Put('/:loaId') @UseInterceptors(FileInterceptor('file'), JsonReqBodyInterceptor) async update( @@ -174,7 +195,12 @@ export class LoaController { summary: 'Delete LoA by Id.', description: 'Deletes the LoA object from the database.', }) - @Permissions({ claim: Claim.WRITE_LOA }) + @Permissions({ + allowedIdirRoles: [ + IDIRUserRole.HQ_ADMINISTRATOR, + IDIRUserRole.SYSTEM_ADMINISTRATOR, + ], + }) @Delete('/:loaId') async delete( @Req() request: Request, @@ -189,7 +215,10 @@ export class LoaController { summary: 'Get LoA Document', description: 'Retrieve the LoA document from the database.', }) - @Permissions({ claim: Claim.READ_LOA }) + @Permissions({ + allowedIdirRoles: IDIR_USER_ROLE_LIST, + allowedBCeIDRoles: CLIENT_USER_ROLE_LIST, + }) @Get('/:loaId/documents') async getLoaDocument( @Req() request: Request, @@ -215,7 +244,12 @@ export class LoaController { summary: 'Delete LoA Document', description: 'Deletes the LoA document from the database.', }) - @Permissions({ claim: Claim.WRITE_LOA }) + @Permissions({ + allowedIdirRoles: [ + IDIRUserRole.HQ_ADMINISTRATOR, + IDIRUserRole.SYSTEM_ADMINISTRATOR, + ], + }) @Delete('/:loaId/documents') async deleteLoaDocument( @Req() request: Request, diff --git a/vehicles/src/modules/special-auth/special-auth.controller.ts b/vehicles/src/modules/special-auth/special-auth.controller.ts index 0df4088ac..9bd7e4b23 100644 --- a/vehicles/src/modules/special-auth/special-auth.controller.ts +++ b/vehicles/src/modules/special-auth/special-auth.controller.ts @@ -18,8 +18,12 @@ import { Request } from 'express'; import { CreateLcvDto } from './dto/request/create-lcv.dto'; import { CreateNoFeeDto } from './dto/request/create-no-fee.dto'; import { Permissions } from '../../common/decorator/permissions.decorator'; -import { Claim } from '../../common/enum/claims.enum'; import { IsFeatureFlagEnabled } from 'src/common/decorator/is-feature-flag-enabled.decorator'; +import { + CLIENT_USER_ROLE_LIST, + IDIR_USER_ROLE_LIST, + IDIRUserRole, +} from 'src/common/enum/user-role.enum'; @ApiBearerAuth() @ApiTags('Special Authorization') @@ -48,7 +52,10 @@ export class SpecialAuthController { description: 'Returns all special authorizations for a company in the database.', }) - @Permissions({ claim: Claim.READ_SPECIAL_AUTH }) + @Permissions({ + allowedIdirRoles: IDIR_USER_ROLE_LIST, + allowedBCeIDRoles: CLIENT_USER_ROLE_LIST, + }) @Get() async get( @Param() { companyId }: CompanyIdPathParamDto, @@ -65,7 +72,12 @@ export class SpecialAuthController { description: 'LCV allowance updated successfully.', type: ReadSpecialAuthDto, }) - @Permissions({ claim: Claim.WRITE_LCV_FLAG }) + @Permissions({ + allowedIdirRoles: [ + IDIRUserRole.HQ_ADMINISTRATOR, + IDIRUserRole.SYSTEM_ADMINISTRATOR, + ], + }) @Put('/lcv') async updateLcv( @Req() request: Request, @@ -89,7 +101,12 @@ export class SpecialAuthController { description: 'No fee type updated successfully.', type: ReadSpecialAuthDto, }) - @Permissions({ claim: Claim.WRITE_NOFEE }) + @Permissions({ + allowedIdirRoles: [ + IDIRUserRole.HQ_ADMINISTRATOR, + IDIRUserRole.SYSTEM_ADMINISTRATOR, + ], + }) @Put('/no-fee') async updateNoFee( @Req() request: Request, From 38456ec4f800680dd16db72079a2c1eb83b9562b Mon Sep 17 00:00:00 2001 From: gchauhan-aot <113390759+gchauhan-aot@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:09:12 -0600 Subject: [PATCH 30/58] LoA number change (#1626) --- vehicles/src/modules/special-auth/loa.service.ts | 1 + vehicles/src/modules/special-auth/profile/loa.profile.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/vehicles/src/modules/special-auth/loa.service.ts b/vehicles/src/modules/special-auth/loa.service.ts index 9ad05b754..8363942d5 100644 --- a/vehicles/src/modules/special-auth/loa.service.ts +++ b/vehicles/src/modules/special-auth/loa.service.ts @@ -311,6 +311,7 @@ export class LoaService { companyId: companyId, documentId: documentId, isActive: true, + loaNumber: existingLoaDetail.loaNumber, previousLoaId: existingLoaDetail.loaId, originalLoaId: existingLoaDetail.originalLoaId, userName: currentUser.userName, diff --git a/vehicles/src/modules/special-auth/profile/loa.profile.ts b/vehicles/src/modules/special-auth/profile/loa.profile.ts index 6f38ecef7..a48e44ac0 100644 --- a/vehicles/src/modules/special-auth/profile/loa.profile.ts +++ b/vehicles/src/modules/special-auth/profile/loa.profile.ts @@ -211,6 +211,12 @@ export class LoaProfile extends AutomapperProfile { return companyId; }), ), + forMember( + (d) => d.loaNumber, + mapWithArguments((_, { loaNumber }) => { + return loaNumber; + }), + ), forMember( (d) => d.documentId, mapWithArguments((_, { documentId }) => { From 7851a62e8c9ec3b855ec88978b2839934f42d868 Mon Sep 17 00:00:00 2001 From: Derek Roberts Date: Thu, 3 Oct 2024 12:35:44 -0700 Subject: [PATCH 31/58] chore(ci): handle gh runner oc removal (#1625) --- .github/workflows/deploy.yml | 5 +++++ .github/workflows/pr-close.yml | 2 +- .github/workflows/scheduled.yml | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6f27faab9..8258f496b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -148,6 +148,11 @@ jobs: # If here skip deployment echo "No triggers have fired, deployment skipped" + - uses: redhat-actions/openshift-tools-installer@v1 + if: ${{ steps.triggers.outputs.triggered == 'true' }} + with: + oc: "4" + - name: Deploy if Triggers Fired if: ${{ steps.triggers.outputs.triggered == 'true' }} working-directory: ${{ inputs.directory }} diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml index 45802f6f9..5b66b040a 100644 --- a/.github/workflows/pr-close.yml +++ b/.github/workflows/pr-close.yml @@ -12,7 +12,7 @@ concurrency: jobs: cleanup: name: Cleanup OpenShift and/or Promote Images - uses: bcgov/quickstart-openshift-helpers/.github/workflows/.pr-close.yml@v0.6.1 + uses: bcgov/quickstart-openshift-helpers/.github/workflows/.pr-close.yml@v0.7.1 secrets: oc_namespace: ${{ vars.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index 98201e02b..5ee888e79 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -80,6 +80,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: + - uses: redhat-actions/openshift-tools-installer@v1 + with: + oc: "4" + - name: Clean up Helm Releases run: | # Clean up Helm Releases From 743d2676c5d286bd86433cea819c93e322f27d11 Mon Sep 17 00:00:00 2001 From: John Fletcher <113134542+john-fletcher-aot@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:16:05 -0700 Subject: [PATCH 32/58] docs: Move database history table docs to git (#1628) --- .../generate-history-tables-and-triggers.sql | 370 ++++++++++++++++++ .../scripts/history-tables-instructions.md | 35 ++ 2 files changed, 405 insertions(+) create mode 100644 database/mssql/scripts/generate-history-tables-and-triggers.sql create mode 100644 database/mssql/scripts/history-tables-instructions.md diff --git a/database/mssql/scripts/generate-history-tables-and-triggers.sql b/database/mssql/scripts/generate-history-tables-and-triggers.sql new file mode 100644 index 000000000..c91974da6 --- /dev/null +++ b/database/mssql/scripts/generate-history-tables-and-triggers.sql @@ -0,0 +1,370 @@ +SET XACT_ABORT, NOCOUNT ON + +-- -------------------------------------------------------------------------------------------- +-- Drop function fnFirsties +-- -------------------------------------------------------------------------------------------- +IF OBJECT_ID ( 'fnFirsties', 'FN' ) IS NOT NULL + drop FUNCTION dbo.fnFirsties; +go + +-- -------------------------------------------------------------------------------------------- +-- Create function fnFirsties +-- -------------------------------------------------------------------------------------------- +CREATE FUNCTION dbo.fnFirsties ( @str NVARCHAR(4000) ) +RETURNS NVARCHAR(max) +AS +BEGIN + DECLARE @retval NVARCHAR(2000); + + SET @str = RTRIM(LTRIM(@str)); + SET @retval = LEFT(@str, 1); + + WHILE CHARINDEX(' ',@str,1)>0 BEGIN + SET @str = LTRIM(RIGHT(@str, LEN(@str) - CHARINDEX(' ', @str, 1))); + SET @retval = LEFT(@str, 1); + END + + RETURN @retval; +END +GO + +-- -------------------------------------------------------------------------------------------- +-- Drop function fnGetColumnsString +-- -------------------------------------------------------------------------------------------- +IF OBJECT_ID ( 'fnGetColumnsString', 'FN' ) IS NOT NULL + drop FUNCTION dbo.fnGetColumnsString; +go + +-- -------------------------------------------------------------------------------------------- +-- Create function fnGetColumnsString +-- -------------------------------------------------------------------------------------------- +CREATE FUNCTION dbo.fnGetColumnsString ( @str NVARCHAR(4000) ) +RETURNS NVARCHAR(max) +AS +BEGIN + DECLARE @retval NVARCHAR(max) = ''; + + declare @statement_sql cursor + set @statement_sql = cursor for + select COLUMN_NAME + , data_type + , CHARACTER_MAXIMUM_LENGTH + , IS_NULLABLE + from INFORMATION_SCHEMA.COLUMNS + where TABLE_NAME = @str + and DATA_TYPE != 'varbinary' + and coalesce(character_maximum_length, 1) != -1 + order by table_name + , ORDINAL_POSITION; + + declare @sql_txt1 nvarchar(max) = ''; + declare @sql_txt2 nvarchar(max) = ''; + declare @sql_txt3 nvarchar(max) = ''; + declare @sql_txt4 nvarchar(max) = ''; + + OPEN @statement_sql + + FETCH NEXT + FROM @statement_sql INTO @sql_txt1, @sql_txt2, @sql_txt3, @sql_txt4 + + WHILE @@FETCH_STATUS = 0 + BEGIN + set @retval += ', [' + @sql_txt1 + '] ' + @sql_txt2 + case when @sql_txt2 in ('char', 'nchar', 'varchar','nvarchar') then '(' + @sql_txt3 + ')' else '' end +case when @sql_txt4 = 'NO' then ' NOT' else '' end+' NULL'; + FETCH NEXT + FROM @statement_sql INTO @sql_txt1, @sql_txt2, @sql_txt3, @sql_txt4 + END + + CLOSE @statement_sql + DEALLOCATE @statement_sql + + RETURN @retval; +END +GO + +--:OUT "G:\OneDrive\Documents\LMS Biz\Quartech\MOTI\PSP\Data Model\Build\Working\ORBC_generate_history_table.sql" + +begin +declare @statement_sql cursor +set @statement_sql = cursor for + select N'IF OBJECT_ID (''' + isnull(convert(varchar(max), p.value), stuff(dbo.fnFirsties(replace(t.name, '_', ' ')), 1, 1, '') + cast(row_number() over (order by t.name asc) as varchar(max))) + '_A_S_IUD_TR'', ''TR'') IS NOT NULL + drop trigger ' + isnull(convert(varchar(max), p.value), stuff(dbo.fnFirsties(replace(t.name, '_', ' ')), 1, 1, '') + cast(row_number() over (order by t.name asc) as varchar(max))) + '_A_S_IUD_TR +go + +IF OBJECT_ID (''' + t.name + '_HIST'', ''U'') IS NOT NULL + drop table ' + t.name + '_HIST +go + +IF OBJECT_ID (''' + t.name + '_H_ID_SEQ'', ''SO'') IS NOT NULL + drop sequence ' + t.name + '_H_ID_SEQ +go + +' +from sys.tables t join + sys.schemas s on s.schema_id = t.schema_id left join + (select table_alias COLLATE catalog_default value + , table_name COLLATE catalog_default table_name + from ORBCX_TableDefinitions) p on t.name COLLATE catalog_default = p.table_name COLLATE catalog_default, + INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc -- INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME +WHERE t.TYPE_desc = 'USER_TABLE' + and t.NAME like 'ORBC_%' + and tc.table_name = t.name + and tc.CONSTRAINT_TYPE = 'PRIMARY KEY' + and t.NAME not like '%HIST' + and t.NAME not like 'ORBCX%' + and t.NAME not like '%EFMigration%' + and t.NAME not like 'ETL%' + and t.NAME not like 'PMBC%' + and t.NAME not like '%SYS_VERS%' + -- code value (type and subtype) tables + and t.NAME not like '%TYPE' + and t.NAME not like '%SUBTYPE' + -- tables that do no have APP audit attributes + and t.name != 'ORBC_COUNTRY' + and t.name != 'ORBC_DISTRICT' + and t.name != 'ORBC_PROVINCE_STATE' + and t.name != 'ORBC_REGION' + and t.name != 'ORBC_TENANT' + and s.name != 'tps' +order by t.name; + +declare @sql_txt varchar(max) = ''; + +OPEN @statement_sql + +FETCH NEXT +FROM @statement_sql INTO @sql_txt + +WHILE @@FETCH_STATUS = 0 + BEGIN + print @sql_txt + FETCH NEXT + FROM @statement_sql INTO @sql_txt + END + +CLOSE @statement_sql +DEALLOCATE @statement_sql + +set @statement_sql = cursor for +select N'CREATE SEQUENCE [' + s.name + '].[' + t.name + '_H_ID_SEQ] AS [bigint] START WITH 1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 50; + +CREATE TABLE [' + s.name + '].[' + t.name + '_HIST]( + '+substring(t.name, 5, len(t.name)) + '_HIST_ID [bigint] DEFAULT (NEXT VALUE FOR [' + s.name + '].[' + t.name + '_H_ID_SEQ]) NOT NULL + ,EFFECTIVE_DATE_HIST [datetime] NOT NULL default getutcdate() + ,END_DATE_HIST [datetime] + '+dbo.fnGetColumnsString(t.name)+' + ) +ALTER TABLE [' + s.name + '].[' + t.name + '_HIST] ADD CONSTRAINT ' + isnull(convert(varchar(max), substring(cast(p.value as varchar), 1, 11)), 'ORBC_' + stuff(dbo.fnFirsties(replace(t.name, '_', ' ')), 1, 1, '') + cast(row_number() over (order by t.name asc) as varchar(max))) + '_H_PK PRIMARY KEY CLUSTERED (' + substring(t.name, 5, len(t.name)) + '_HIST_ID); +ALTER TABLE [' + s.name + '].[' + t.name + '_HIST] ADD CONSTRAINT ' + isnull(convert(varchar(max), substring(cast(p.value as varchar), 1, 11)), 'ORBC_' + stuff(dbo.fnFirsties(replace(t.name, '_', ' ')), 1, 1, '') + cast(row_number() over (order by t.name asc) as varchar(max))) + '_H_UK UNIQUE (' + substring(t.name, 5, len(t.name)) + '_HIST_ID,END_DATE_HIST) +go + +' +from sys.tables t join + sys.schemas s on s.schema_id = t.schema_id left join + (select table_alias COLLATE catalog_default value + , table_name COLLATE catalog_default table_name + from ORBCX_TableDefinitions) p on t.name COLLATE catalog_default = p.table_name COLLATE catalog_default, + INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc --INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME +WHERE t.TYPE_desc = 'USER_TABLE' + and t.NAME like 'ORBC_%' + and tc.table_name = t.name + and tc.CONSTRAINT_TYPE = 'PRIMARY KEY' + and t.NAME not like '%HIST' + and t.NAME not like 'ORBCX%' + and t.NAME not like '%EFMigration%' + and t.NAME not like 'ETL%' + and t.NAME not like 'PMBC%' + and t.NAME not like '%SYS_VERS%' + -- code value (type and subtype) tables + and t.NAME not like '%TYPE' + and t.NAME not like '%SUBTYPE' + -- tables that do no have APP audit attributes + and t.name != 'ORBC_COUNTRY' + and t.name != 'ORBC_DISTRICT' + and t.name != 'ORBC_PROVINCE_STATE' + and t.name != 'ORBC_REGION' + and t.name != 'ORBC_TENANT' + and s.name != 'tps' +order by 1; + +OPEN @statement_sql +FETCH NEXT +FROM @statement_sql INTO @sql_txt +WHILE @@FETCH_STATUS = 0 +BEGIN + print @sql_txt + FETCH NEXT + FROM @statement_sql INTO @sql_txt +END + +CLOSE @statement_sql +DEALLOCATE @statement_sql + +end +-- ............................................................................................ + +--drop function dbo.fnFirsties; +--drop function fnGetColumnsString; + +------------------------------------------------ +SET XACT_ABORT, NOCOUNT ON + + +-- -------------------------------------------------------------------------------------------- +-- Drop function fnFirsties +-- -------------------------------------------------------------------------------------------- +IF OBJECT_ID ( 'fnFirsties', 'FN' ) IS NOT NULL + drop FUNCTION dbo.fnFirsties; +go +-- ............................................................................................ + + +-- -------------------------------------------------------------------------------------------- +-- Create function fnFirsties +-- -------------------------------------------------------------------------------------------- +CREATE FUNCTION dbo.fnFirsties ( @str NVARCHAR(4000) ) +RETURNS NVARCHAR(max) +AS +BEGIN + DECLARE @retval NVARCHAR(2000); + + SET @str=RTRIM(LTRIM(@str)); + SET @retval=LEFT(@str,1); + + WHILE CHARINDEX(' ',@str,1)>0 BEGIN + SET @str=LTRIM(RIGHT(@str,LEN(@str)-CHARINDEX(' ',@str,1))); + SET @retval+=LEFT(@str,1); + END + + RETURN @retval; +END +GO +-- ............................................................................................ + + +-- -------------------------------------------------------------------------------------------- +-- Drop function fnGetColumnsString2 +-- -------------------------------------------------------------------------------------------- +IF OBJECT_ID ( 'fnGetColumnsString2', 'FN' ) IS NOT NULL + drop FUNCTION dbo.fnGetColumnsString2; +go +-- ............................................................................................ + + +-- -------------------------------------------------------------------------------------------- +-- Create function fnGetColumnsString2 +-- -------------------------------------------------------------------------------------------- +CREATE FUNCTION dbo.fnGetColumnsString2 ( @str NVARCHAR(4000) ) +RETURNS NVARCHAR(max) +AS +BEGIN + DECLARE @retval NVARCHAR(max) = ''; + + declare @statement_sql cursor + set @statement_sql = cursor for + select COLUMN_NAME from INFORMATION_SCHEMA.columns + where TABLE_NAME = @str + and DATA_TYPE != 'varbinary' and coalesce(character_maximum_length,1) != -1 + order by table_name, ORDINAL_POSITION; + + declare @sql_txt1 nvarchar(max) = ''; + OPEN @statement_sql + FETCH NEXT + FROM @statement_sql INTO @sql_txt1 + WHILE @@FETCH_STATUS = 0 + begin + set @retval = @retval + '['+ @sql_txt1+'], '; + FETCH NEXT + FROM @statement_sql INTO @sql_txt1 + END + + CLOSE @statement_sql + DEALLOCATE @statement_sql + + RETURN @retval; +END +GO +-- ............................................................................................ + +--:OUT "G:\OneDrive\Documents\LMS Biz\Quartech\MOTI\PSP\Data Model\Build\Working\ORBC_iud_trigger.sql" + +begin + +declare @statement_sql cursor +declare @sql_txt varchar(max) = ''; +--declare @previous_sql_txt varchar(max) = ''; + +set @statement_sql = cursor for +select distinct N'CREATE TRIGGER '+isnull(convert(varchar(max),p.value), stuff(dbo.fnFirsties(replace(t.name,'_',' ')),1,1,'')+cast(row_number()over(order by t.name asc) as varchar(max)))+'_A_S_IUD_TR ON [' + s.name + '].[' + t.name + '] FOR INSERT, UPDATE, DELETE AS +SET NOCOUNT ON +BEGIN TRY +DECLARE @curr_date datetime; +SET @curr_date = getutcdate(); + IF NOT EXISTS(SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted) + RETURN; + + -- historical + IF EXISTS(SELECT * FROM deleted) + update [' + s.name + '].[' + t.name + '_HIST] set END_DATE_HIST = @curr_date where ' + ccu.COLUMN_NAME + ' in (select ' + ccu.COLUMN_NAME + ' from deleted) and END_DATE_HIST is null; + + IF EXISTS(SELECT * FROM inserted) + insert into [' + s.name + '].[' + t.name + '_HIST] (' + dbo.fnGetColumnsString2(t.name) + substring(t.name, 5, len(t.name)) + '_HIST_ID, END_DATE_HIST, EFFECTIVE_DATE_HIST) + select ' + dbo.fnGetColumnsString2(t.name) + '(next value for [' + s.name + '].[' + t.name + '_H_ID_SEQ]) as ['+substring(t.name, 5, len(t.name)) + '_HIST_ID], null as [END_DATE_HIST], @curr_date as [EFFECTIVE_DATE_HIST] from inserted; + +END TRY +BEGIN CATCH + IF @@trancount > 0 ROLLBACK TRANSACTION + EXEC orbc_error_handling +END CATCH; +go + +' +from sys.tables t join + sys.schemas s on s.schema_id = t.schema_id left join + (select table_alias COLLATE catalog_default value + , table_name COLLATE catalog_default table_name + from ORBCX_TableDefinitions) p on t.name COLLATE catalog_default = p.table_name COLLATE catalog_default, + INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME +WHERE t.TYPE_desc = 'USER_TABLE' + and t.NAME like 'ORBC_%' + and tc.table_name = t.name + and tc.CONSTRAINT_TYPE = 'PRIMARY KEY' + and t.NAME not like '%HIST' + and t.NAME not like 'ORBCX%' + and t.NAME not like '%EFMigration%' + and t.NAME not like 'ETL%' + and t.NAME not like 'PMBC%' + and t.NAME not like '%SYS_VERS%' + -- code value (type and subtype) tables + and t.NAME not like '%TYPE' + and t.NAME not like '%SUBTYPE' + -- tables that do no have APP audit attributes + and t.name != 'ORBC_COUNTRY' + and t.name != 'ORBC_DISTRICT' + and t.name != 'ORBC_PROVINCE_STATE' + and t.name != 'ORBC_REGION' + and t.name != 'ORBC_TENANT' + and s.name != 'tps' +order by 1; + +OPEN @statement_sql +FETCH NEXT +FROM @statement_sql INTO @sql_txt +WHILE @@FETCH_STATUS = 0 +BEGIN + print @sql_txt + FETCH NEXT + FROM @statement_sql INTO @sql_txt +END + +CLOSE @statement_sql +DEALLOCATE @statement_sql + +end + +-- -------------------------------------------------------------------------------------------- +-- Drop functions created earlier +-- -------------------------------------------------------------------------------------------- + +drop function dbo.fnFirsties; +drop function fnGetColumnsString; +drop function fnGetColumnsString2; \ No newline at end of file diff --git a/database/mssql/scripts/history-tables-instructions.md b/database/mssql/scripts/history-tables-instructions.md new file mode 100644 index 000000000..08a1282c6 --- /dev/null +++ b/database/mssql/scripts/history-tables-instructions.md @@ -0,0 +1,35 @@ +# History Tables and Triggers +MoTI database standards dictate a specific approach for maintaining history of database changes. This is handled in ORBC with 'mirror' history tables, named identically to the base table with a _HIST suffix. + +The history tables are populated on each insert, update, and delete operation via a trigger on the base table which inserts a new row into the history table for every action. + +The history tables rely on a database sequence to generate their unique identifier (as opposed to identity columns which are typically used in ORBC for this purpose in the base tables). + +## New Tables +Each new table created in the ORBC database which is not a 'lookup' or 'type' table will need three additional objects created alongside: a history table, a trigger, and a sequence. + +For new tables, the SQL to generate these three objects can be retrieved by running the generate-history-tables-and-triggers.sql file in your local (docker) development database once the new business tables are in place. The .sql file will generate the DDL for all base tables in the database, not just the new ones you've added, so you will need to search the output for your new table name to find the three objects. You can ignore the rest. + +The name of the trigger that the .sql generates in the DDL is not appropriate - you will need to come up with an appropriate name for the trigger that is based on a shortened version of the base table name. Refer to the other triggers in the database for guidance - it isn't critical that it be done a certain way just that it be fairly consistent with the rest of the trigger names, and that it be unique in the database. + +## Updated Tables +When a table is updated in ORBC to add or remove columns, to change the data type of the column, or to change a column from nullable to non-nullable, more manual effort is required. + +__Important__: when copying the DDL that's generated from the attached .sql script, __do not__ copy the 'drop table' or 'drop sequence' components. History tables, once modified, are to remain permanently in the database. For sequences, if it is dropped and re-created it will start at 1 again which will generate errors. + +### Column Additions +New columns must be added to both the history table and the trigger. You can use the generated DDL for guidance with how to add it, or you can add it in manually if just one or two columns (probably easier that way). + +### Column Deletions +Deleted columns must be removed from the trigger, but should remain in the history table. Never delete any columns with data from the history tables, they will remain in perpetuity. + +### Column Renames +However the column rename is done in the base table DDL is how it should be done also in the history table DDL. The trigger will also need to be updated to reflect the new column name, though this is just a text modification to the trigger DDL and not as complex as the table rename. + +### Column Data Type Changes +As with the column renames, the column data type changes must be done in the history table in the same way as it is done in the base table (avoidance of data loss / truncation is important). No changes need to be made to the trigger DDL. + +### Column NULL / NOT NULL Switches +Columns changing from NOT NULL to NULL require that the same change be done to the history table. No changes are required to the trigger. + +Columns changing from NULL to NOT NULL require __no__ changes to the history table to avoid creating fake 'history' data where null is appropriate. No changes are required to the trigger. \ No newline at end of file From 97e4370d30e88a1edbf0da8f81ec6be1e599b1c6 Mon Sep 17 00:00:00 2001 From: John Fletcher <113134542+john-fletcher-aot@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:26:14 -0700 Subject: [PATCH 33/58] feat: Increment version for policy-engine for NPM package (#1629) --- policy-engine/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policy-engine/package.json b/policy-engine/package.json index 55ac20d00..7a846121b 100644 --- a/policy-engine/package.json +++ b/policy-engine/package.json @@ -1,6 +1,6 @@ { "name": "onroute-policy-engine", - "version": "0.1.0", + "version": "0.2.0", "description": "Policy Engine library for commercial vehicle permitting", "main": "dist/index.js", "exports": { From 2ef35feb0974a935dc01eec29849857a834447ed Mon Sep 17 00:00:00 2001 From: John Fletcher <113134542+john-fletcher-aot@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:02:06 -0700 Subject: [PATCH 34/58] feat: Refactor for updated STOS JSON data model (#1630) --- policy-engine/src/_examples/validate-invalid-tros.ts | 2 +- .../src/_test/permit-app/permit-application.type.ts | 9 ++++++++- policy-engine/src/_test/permit-app/test-stos.ts | 5 ++++- policy-engine/src/enum/permit-app-info.ts | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/policy-engine/src/_examples/validate-invalid-tros.ts b/policy-engine/src/_examples/validate-invalid-tros.ts index c9c054a0f..df493bc29 100644 --- a/policy-engine/src/_examples/validate-invalid-tros.ts +++ b/policy-engine/src/_examples/validate-invalid-tros.ts @@ -14,7 +14,7 @@ async function start() { // Set duration to 31 days (an invalid duration) validTros30Day.permitData.permitDuration = 31; - + const validationResult2 = await policy.validate(validTros30Day); console.log(JSON.stringify(validationResult2, null, ' ')); } diff --git a/policy-engine/src/_test/permit-app/permit-application.type.ts b/policy-engine/src/_test/permit-app/permit-application.type.ts index 59b78eccb..8f47ff50f 100644 --- a/policy-engine/src/_test/permit-app/permit-application.type.ts +++ b/policy-engine/src/_test/permit-app/permit-application.type.ts @@ -54,12 +54,17 @@ type PermitData = { feeSummary?: string | null; startDate: string; expiryDate?: string | null; - permittedCommodity?: string | null; + permittedCommodity?: PermittedCommodity | null; vehicleConfiguration?: VehicleConfiguration | null; permittedRoute?: PermittedRoute | null; applicationNotes?: string | null; }; +type PermittedCommodity = { + commodityType: string; + loadDescription: string; +}; + type VehicleInConfiguration = { vehicleSubType: string; }; @@ -82,6 +87,8 @@ type ManualRoute = { highwaySequence: Array; origin: string; destination: string; + exitPoint?: string; + totalDistance?: number; }; type PermitApplication = { diff --git a/policy-engine/src/_test/permit-app/test-stos.ts b/policy-engine/src/_test/permit-app/test-stos.ts index 08b33772a..8905363e8 100644 --- a/policy-engine/src/_test/permit-app/test-stos.ts +++ b/policy-engine/src/_test/permit-app/test-stos.ts @@ -43,7 +43,10 @@ export const testStos: PermitApplication = { countryCode: 'CA', postalCode: 'V8B1A2', }, - permittedCommodity: 'EMPTYXX', + permittedCommodity: { + commodityType: 'EMPTYXX', + loadDescription: 'empty', + }, vehicleConfiguration: { overallLength: 25, overallWidth: 3, diff --git a/policy-engine/src/enum/permit-app-info.ts b/policy-engine/src/enum/permit-app-info.ts index bcea9a542..8d4eb0121 100644 --- a/policy-engine/src/enum/permit-app-info.ts +++ b/policy-engine/src/enum/permit-app-info.ts @@ -12,5 +12,5 @@ export enum PermitAppInfo { PermitDuration = '$.permitDuration', PowerUnitType = '$.vehicleDetails.vehicleSubType', TrailerList = '$.vehicleConfiguration.trailers', - Commodity = '$.permittedCommodity', + Commodity = '$.permittedCommodity.commodityType', } From a2d5b1487fd1d098533954e49d1a3f09283d7f73 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:48:59 -0400 Subject: [PATCH 35/58] feat:ORV2-2922: GL Code Update (#1632) --- .../permit.ORBC_PAYMENT_METHOD_TYPE.Table.sql | 4 ++ .../scripts/utility/refresh-paybc-gl-code.sh | 18 +++++++- .../versions/revert/v_45_ddl_revert.sql | 27 +++++++++++ database/mssql/scripts/versions/v_45_ddl.sql | 45 +++++++++++++++++++ database/mssql/test/versions/v_45_1_test.sql | 4 ++ database/mssql/test/versions/v_45_test.sh | 16 +++++++ docker-compose.yml | 1 + vehicles/src/app.service.ts | 6 +++ vehicles/src/common/constants/api.constant.ts | 1 + vehicles/src/common/enum/cache-key.enum.ts | 1 + .../entities/payment-method-type.entity.ts | 4 ++ .../payment/payment.service.ts | 20 ++++++--- 12 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 database/mssql/scripts/sampledata/permit.ORBC_PAYMENT_METHOD_TYPE.Table.sql create mode 100644 database/mssql/scripts/versions/revert/v_45_ddl_revert.sql create mode 100644 database/mssql/scripts/versions/v_45_ddl.sql create mode 100644 database/mssql/test/versions/v_45_1_test.sql create mode 100644 database/mssql/test/versions/v_45_test.sh diff --git a/database/mssql/scripts/sampledata/permit.ORBC_PAYMENT_METHOD_TYPE.Table.sql b/database/mssql/scripts/sampledata/permit.ORBC_PAYMENT_METHOD_TYPE.Table.sql new file mode 100644 index 000000000..68585ecfe --- /dev/null +++ b/database/mssql/scripts/sampledata/permit.ORBC_PAYMENT_METHOD_TYPE.Table.sql @@ -0,0 +1,4 @@ +SET NOCOUNT ON +GO +UPDATE [permit].[ORBC_PAYMENT_METHOD_TYPE] SET GL_PROJ_CODE='$(GL_PROJ_CODE)' WHERE PAYMENT_METHOD_TYPE= '$(PAYMENT_METHOD_TYPE)' +GO \ No newline at end of file diff --git a/database/mssql/scripts/utility/refresh-paybc-gl-code.sh b/database/mssql/scripts/utility/refresh-paybc-gl-code.sh index 73802a0ae..de6fdd70c 100644 --- a/database/mssql/scripts/utility/refresh-paybc-gl-code.sh +++ b/database/mssql/scripts/utility/refresh-paybc-gl-code.sh @@ -5,12 +5,14 @@ source ${SCRIPT_DIR}/utility/getopt.sh USAGE="-u ORBC_USER -p ORBC_PASS -s ORBC_SERVER -d ORBC_DATABASE" parse_options "${USAGE}" ${@} -# Clear out all records from the pending IDIR users table +# Clear out GL Codes from the permit type table sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -Q "SET NOCOUNT ON; UPDATE permit.ORBC_PERMIT_TYPE SET GL_CODE=NULL" +# Clear out GL Proj Codes from the payment method type table +sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -Q "SET NOCOUNT ON; UPDATE permit.ORBC_PAYMENT_METHOD_TYPE SET GL_PROJ_CODE=NULL" ( # PAYBC_GL_CODE environment variable must be a string representing an array -# of tuples in the format PERMIT_TUPE,GL_CODE separated by single space characters. +# of tuples in the format PERMIT_TYPE,GL_CODE separated by single space characters. # For example "TROS,000.00000.00000.0000.0000000.000000.0000 TROW,EOF000.00000.00000.0000.0000000.000000.0000" for i in ${PAYBC_GL_CODE} do @@ -19,3 +21,15 @@ do sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -v PERMIT_TYPE=${1} GL_CODE=${2} -i ${SCRIPT_DIR}/sampledata/permit.ORBC_PERMIT_TYPE.Table.sql done ) + +( +# GL_PROJ_CODE environment variable must be a string representing an array +# of tuples in the format PAYMENT_METHOD_TYPE,GL_PROJ_CODE separated by single space characters. +# For example "WEB,0000000 ICEPAY,0000000" +for i in ${GL_PROJ_CODE} +do + IFS=","; set -- $i + echo "Updating payment method type ${1} with GL_PROJ_CODE ${2}" + sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -v PAYMENT_METHOD_TYPE=${1} GL_PROJ_CODE=${2} -i ${SCRIPT_DIR}/sampledata/permit.ORBC_PAYMENT_METHOD_TYPE.Table.sql +done +) diff --git a/database/mssql/scripts/versions/revert/v_45_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_45_ddl_revert.sql new file mode 100644 index 000000000..668f442d2 --- /dev/null +++ b/database/mssql/scripts/versions/revert/v_45_ddl_revert.sql @@ -0,0 +1,27 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON + +BEGIN TRY + BEGIN TRANSACTION + + ALTER TABLE [permit].[ORBC_PAYMENT_METHOD_TYPE] DROP COLUMN [GL_PROJ_CODE]; + + COMMIT +END TRY + +BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK; + THROW +END CATCH + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Reverting addition of GL_PROJ_CODE to ORBC_PAYMENT_METHOD_TYPE.' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (44, @VersionDescription, getutcdate()) diff --git a/database/mssql/scripts/versions/v_45_ddl.sql b/database/mssql/scripts/versions/v_45_ddl.sql new file mode 100644 index 000000000..9dc5a5430 --- /dev/null +++ b/database/mssql/scripts/versions/v_45_ddl.sql @@ -0,0 +1,45 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO + +IF @@ERROR <> 0 SET NOEXEC ON +GO +ALTER TABLE [permit].[ORBC_PAYMENT_METHOD_TYPE] ADD [GL_PROJ_CODE] [char] (7) NULL +GO + +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'GL Project Code' , @level0type=N'SCHEMA',@level0name=N'permit', @level1type=N'TABLE',@level1name=N'ORBC_PAYMENT_METHOD_TYPE', @level2type=N'COLUMN',@level2name=N'GL_PROJ_CODE' +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Add GL_PROJ_CODE col to ORBC_PAYMENT_METHOD_TYPE' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (45, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO + diff --git a/database/mssql/test/versions/v_45_1_test.sql b/database/mssql/test/versions/v_45_1_test.sql new file mode 100644 index 000000000..ce6543ca4 --- /dev/null +++ b/database/mssql/test/versions/v_45_1_test.sql @@ -0,0 +1,4 @@ +-- Test that the GL_PROJ_CODE column has been added correctly +SET NOCOUNT ON + +select COL_LENGTH('$(DB_NAME).[permit].[ORBC_PAYMENT_METHOD_TYPE]', 'GL_PROJ_CODE') diff --git a/database/mssql/test/versions/v_45_test.sh b/database/mssql/test/versions/v_45_test.sh new file mode 100644 index 000000000..e9e2da838 --- /dev/null +++ b/database/mssql/test/versions/v_45_test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Retrieve arguments +source ${SCRIPT_DIR}/utility/getopt.sh +USAGE="-u USER -p PASS -s SERVER -d DATABASE" +parse_options "${USAGE}" ${@} + +# All database tests for database version 45 are run from this shell script. +# TESTS_DIR variable set by the calling test-runner script. + +TEST_45_1_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_45_1_test.sql | xargs) +if [[ $TEST_45_1_RESULT -eq 7 ]]; then + echo "Test 45.1 passed: GL_PROJ_CODE column created in ORBC_PAYMENT_METHOD_TYPE" +else + echo "******** Test 45.1 failed: GL_PROJ_CODE column missing in ORBC_PAYMENT_METHOD_TYPE" +fi \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d008896f0..46f68d2e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,7 @@ services: MSSQL_RUN_TESTS: ${MSSQL_RUN_TESTS} SAMPLE_PENDING_IDIR_USERS: ${SAMPLE_PENDING_IDIR_USERS} PAYBC_GL_CODE: ${PAYBC_GL_CODE} + GL_PROJ_CODE: ${GL_PROJ_CODE} MSSQL_LOAD_SAMPLE_DATA: ${MSSQL_LOAD_SAMPLE_DATA} MSSQL_MOTI_HOST: ${MSSQL_MOTI_HOST} MSSQL_MOTI_DB: ${MSSQL_MOTI_DB} diff --git a/vehicles/src/app.service.ts b/vehicles/src/app.service.ts index ac139a4b7..bcee88529 100644 --- a/vehicles/src/app.service.ts +++ b/vehicles/src/app.service.ts @@ -91,6 +91,12 @@ export class AppService { createCacheMap(paymentMethods, 'paymentMethodTypeCode', 'name'), ); + await addToCache( + this.cacheManager, + CacheKey.PAYMENT_METHOD_TYPE_GL_PROJ_CODE, + createCacheMap(paymentMethods, 'paymentMethodTypeCode', 'glProjCode'), + ); + const paymentTypes = await this.paymentService.findAllPaymentCardTypeEntities(); await addToCache( diff --git a/vehicles/src/common/constants/api.constant.ts b/vehicles/src/common/constants/api.constant.ts index 1bb64594f..189522ecf 100644 --- a/vehicles/src/common/constants/api.constant.ts +++ b/vehicles/src/common/constants/api.constant.ts @@ -8,3 +8,4 @@ export const CRYPTO_ALGORITHM_MD5 = 'md5'; export const CRYPTO_ALGORITHM_SHA256 = 'sha256'; export const TOKEN_EXPIRY_BUFFER = 15; export const PERMISSIONS_KEY = 'permissions'; +export const GL_PROJ_CODE_PLACEHOLDER = 'PROJECT'; diff --git a/vehicles/src/common/enum/cache-key.enum.ts b/vehicles/src/common/enum/cache-key.enum.ts index 91398e2e1..82100c8cc 100644 --- a/vehicles/src/common/enum/cache-key.enum.ts +++ b/vehicles/src/common/enum/cache-key.enum.ts @@ -3,6 +3,7 @@ export enum CacheKey { PROVINCE = 'PROVINCE', PERMIT_TYPE = 'PERMIT_TYPE', PERMIT_TYPE_GL_CODE = 'PERMIT_TYPE_GL_CODE', + PAYMENT_METHOD_TYPE_GL_PROJ_CODE = 'PAYMENT_METHOD_TYPE_GL_PROJ_CODE', VEHICLE_TYPE = 'VEHICLE_TYPE', POWER_UNIT_TYPE = 'POWER_UNIT_TYPE', TRAILER_TYPE = 'TRAILER_TYPE', diff --git a/vehicles/src/modules/permit-application-payment/payment/entities/payment-method-type.entity.ts b/vehicles/src/modules/permit-application-payment/payment/entities/payment-method-type.entity.ts index c5e26aa97..55bec5b4c 100644 --- a/vehicles/src/modules/permit-application-payment/payment/entities/payment-method-type.entity.ts +++ b/vehicles/src/modules/permit-application-payment/payment/entities/payment-method-type.entity.ts @@ -14,4 +14,8 @@ export class PaymentMethodType { @AutoMap() @Column({ name: 'DESCRIPTION', nullable: true, length: 50 }) description: string; + + @AutoMap() + @Column({ name: 'GL_PROJ_CODE', nullable: true, length: 7 }) + glProjCode: string; } diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts index 3c82633d9..3d00ba53d 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts @@ -35,6 +35,7 @@ import { PAYBC_PAYMENT_METHOD, PAYMENT_CURRENCY, CRYPTO_ALGORITHM_MD5, + GL_PROJ_CODE_PLACEHOLDER, } from '../../../common/constants/api.constant'; import { convertToHash } from 'src/common/helper/crypto.helper'; import { UpdatePaymentGatewayTransactionDto } from './dto/request/update-payment-gateway-transaction.dto'; @@ -106,15 +107,24 @@ export class PaymentService { private queryHash = async (transaction: Transaction) => { const redirectUrl = process.env.PAYBC_REDIRECT; const date = new Date().toISOString().split('T')[0]; + const glProjCode = await getFromCache( + this.cacheManager, + CacheKey.PAYMENT_METHOD_TYPE_GL_PROJ_CODE, + transaction.paymentMethodTypeCode, + ); const glCodeDetails = await Promise.all( transaction.permitTransactions.map( async ({ permit: { permitType }, transactionAmount }) => ({ - glCode: await getFromCache( - this.cacheManager, - CacheKey.PERMIT_TYPE_GL_CODE, - permitType, - ), + glCode: ( + await getFromCache( + this.cacheManager, + CacheKey.PERMIT_TYPE_GL_CODE, + permitType, + ) + ) + ?.replace(GL_PROJ_CODE_PLACEHOLDER, glProjCode) + ?.trim(), amount: transactionAmount, }), ), From 82180c0a78cd59a860666934dbb96bfeb8f60518 Mon Sep 17 00:00:00 2001 From: gchauhan-aot <113390759+gchauhan-aot@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:19:28 -0600 Subject: [PATCH 36/58] Orv2 2874 be dates validation (#1631) --- vehicles/src/common/constants/api.constant.ts | 2 + vehicles/src/common/helper/common.helper.ts | 7 +++ .../helper/permit-application.helper.ts | 8 +++ .../src/common/helper/permit-fee.helper.ts | 19 ++++++ .../payment/payment.service.ts | 63 ++++++++++--------- 5 files changed, 71 insertions(+), 28 deletions(-) diff --git a/vehicles/src/common/constants/api.constant.ts b/vehicles/src/common/constants/api.constant.ts index 189522ecf..b15b80cbe 100644 --- a/vehicles/src/common/constants/api.constant.ts +++ b/vehicles/src/common/constants/api.constant.ts @@ -8,4 +8,6 @@ export const CRYPTO_ALGORITHM_MD5 = 'md5'; export const CRYPTO_ALGORITHM_SHA256 = 'sha256'; export const TOKEN_EXPIRY_BUFFER = 15; export const PERMISSIONS_KEY = 'permissions'; +export const TIMEZONE_PACIFIC = "America/Vancouver"; export const GL_PROJ_CODE_PLACEHOLDER = 'PROJECT'; + diff --git a/vehicles/src/common/helper/common.helper.ts b/vehicles/src/common/helper/common.helper.ts index b7f1ed268..05c55fc3d 100644 --- a/vehicles/src/common/helper/common.helper.ts +++ b/vehicles/src/common/helper/common.helper.ts @@ -2,6 +2,7 @@ import { Cache } from 'cache-manager'; import { CacheKey } from '../enum/cache-key.enum'; import { getFromCache } from './cache.helper'; import { FeatureFlagValue } from '../enum/feature-flag-value.enum'; +import { IDP } from '../enum/idp.enum'; /** * Evaluates the given predicate and returns the value if the predicate is true or the value is not null, otherwise returns undefined. @@ -48,3 +49,9 @@ export const isFeatureEnabled = async ( return true; }; + +export const isCVClient = (identityProvider: IDP): boolean => { + return ( + identityProvider !== IDP.IDIR && identityProvider !== IDP.SERVICE_ACCOUNT + ); +}; diff --git a/vehicles/src/common/helper/permit-application.helper.ts b/vehicles/src/common/helper/permit-application.helper.ts index 63b591f2a..b572f57e9 100644 --- a/vehicles/src/common/helper/permit-application.helper.ts +++ b/vehicles/src/common/helper/permit-application.helper.ts @@ -18,6 +18,7 @@ import { User } from '../../modules/company-user-management/users/entities/user. import { ApplicationStatus } from '../enum/application-status.enum'; import { PermitType } from '../enum/permit-type.enum'; import { PERMIT_TYPES_FOR_QUEUE } from '../constants/permit.constant'; +import * as dayjs from 'dayjs'; /** * Fetches and resolves various types of names associated with a permit using cache. @@ -266,3 +267,10 @@ export const isPermitTypeEligibleForQueue = ( ): boolean => { return PERMIT_TYPES_FOR_QUEUE.includes(permitType); }; + +export const validApplicationDates = (application: Permit, timezone: string): boolean => { + const todayUTC = dayjs(new Date()); + const todayPacific = todayUTC.tz(timezone).format("YYYY-MM-DD"); + const { startDate, expiryDate } = application.permitData; + return startDate >= todayPacific && startDate <= expiryDate; +} diff --git a/vehicles/src/common/helper/permit-fee.helper.ts b/vehicles/src/common/helper/permit-fee.helper.ts index 835687f0c..8e2314c02 100644 --- a/vehicles/src/common/helper/permit-fee.helper.ts +++ b/vehicles/src/common/helper/permit-fee.helper.ts @@ -200,3 +200,22 @@ export const calculatePermitAmount = ( return amount; }; + +export const validAmount = ( + calculatedAmount: number, + receivedAmount: number, + transactionType: TransactionType, +): boolean =>{ + const isAmountValid = + receivedAmount.toFixed(2) === Math.abs(calculatedAmount).toFixed(2); + + // For refund transactions, ensure the calculated amount is negative. + const isRefundValid = + calculatedAmount < 0 && transactionType === TransactionType.REFUND; + + // Return true if the amounts are valid or if it's a valid refund transaction + return ( + isAmountValid && + (isRefundValid || transactionType !== TransactionType.REFUND) + ); +} diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts index 3d00ba53d..e4a061f1a 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts @@ -5,6 +5,7 @@ import { InternalServerErrorException, Logger, NotFoundException, + UnprocessableEntityException, } from '@nestjs/common'; import { CreateTransactionDto } from './dto/request/create-transaction.dto'; import { ReadTransactionDto } from './dto/response/read-transaction.dto'; @@ -46,10 +47,11 @@ import { PermitHistoryDto } from '../permit/dto/response/permit-history.dto'; import { calculatePermitAmount, permitFee, + validAmount, } from 'src/common/helper/permit-fee.helper'; import { CfsTransactionDetail } from './entities/cfs-transaction.entity'; import { CfsFileStatus } from 'src/common/enum/cfs-file-status.enum'; -import { isAmendmentApplication } from '../../../common/helper/permit-application.helper'; +import { isAmendmentApplication, validApplicationDates } from '../../../common/helper/permit-application.helper'; import { isCfsPaymentMethodType } from 'src/common/helper/payment.helper'; import { PgApprovesStatus } from 'src/common/enum/pg-approved-status-type.enum'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; @@ -62,8 +64,12 @@ import { throwBadRequestException, throwUnprocessableEntityException, } from '../../../common/helper/exception.helper'; -import { isFeatureEnabled } from '../../../common/helper/common.helper'; +import { + isCVClient, + isFeatureEnabled, +} from '../../../common/helper/common.helper'; import { SpecialAuth } from 'src/modules/special-auth/entities/special-auth.entity'; +import { TIMEZONE_PACIFIC } from 'src/common/constants/api.constant'; @Injectable() export class PaymentService { @@ -323,12 +329,12 @@ export class PaymentService { 'Application in its current status cannot be processed for payment.', ); } - const totalTransactionAmount = - await this.validatePaymentAndCalculateAmount( - createTransactionDto, - existingApplications, - queryRunner, - ); + const totalTransactionAmount = await this.validateApplicationAndPayment( + createTransactionDto, + existingApplications, + currentUser, + queryRunner, + ); const transactionOrderNumber = await this.generateTransactionOrderNumber(); @@ -496,17 +502,27 @@ export class PaymentService { * @throws {BadRequestException} When the transaction amount in the request doesn't match with the calculated amount, * or if there's a transaction type and amount mismatch in case of refunds. */ - private async validatePaymentAndCalculateAmount( + private async validateApplicationAndPayment( createTransactionDto: CreateTransactionDto, applications: Permit[], + currentUser: IUserJWT, queryRunner: QueryRunner, ) { let totalTransactionAmountCalculated = 0; + const isCVClientUser: boolean = isCVClient(currentUser.identity_provider); // Calculate and add amount for each requested application, as per the available backend data. for (const application of applications) { - totalTransactionAmountCalculated = - totalTransactionAmountCalculated + - (await this.permitFeeCalculator(application, queryRunner)); + //Check if each application has a valid start date and valid expiry date. + if (isCVClientUser && !validApplicationDates(application, TIMEZONE_PACIFIC)) + { + throw new UnprocessableEntityException( + `Atleast one of the application has invalid startDate or expiryDate.`, + ); + } + totalTransactionAmountCalculated += await this.permitFeeCalculator( + application, + queryRunner, + ); } const totalTransactionAmount = createTransactionDto.applicationDetails?.reduce( @@ -514,22 +530,13 @@ export class PaymentService { 0, ); if ( - totalTransactionAmount.toFixed(2) != - Math.abs(totalTransactionAmountCalculated).toFixed(2) - ) { - throw new BadRequestException( - `Transaction Amount Mismatch. Amount received is $${totalTransactionAmount.toFixed(2)} but amount calculated is $${Math.abs(totalTransactionAmountCalculated).toFixed(2)}`, - ); - } - - //For transaction type refund, total transaction amount in backend should be less than zero and vice a versa. - //This extra check to compare transaction type and amount is only needed in case of refund, for other trasaction types, comparing amount is sufficient. - if ( - totalTransactionAmountCalculated < 0 && - createTransactionDto.transactionTypeId != TransactionType.REFUND - ) { - throw new BadRequestException('Transaction Type Mismatch'); - } + !validAmount( + totalTransactionAmountCalculated, + totalTransactionAmount, + createTransactionDto.transactionTypeId, + ) + ) + throw new BadRequestException('Transaction amount mismatch.'); return totalTransactionAmount; } From 99a3b586fa1a17b6e661be13decc59f615e29b63 Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:08:45 -0700 Subject: [PATCH 37/58] ORV2-2578 - FE: Staff Manage Queue List (#1627) Co-authored-by: praju-aot Co-authored-by: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Co-authored-by: zgong-gov --- .../common/authentication/LoginRedirect.tsx | 17 +- .../common/authentication/PermissionMatrix.ts | 6 +- .../naviconsidebar/NavIconHomeButton.tsx | 9 +- frontend/src/common/types/common.ts | 19 +- frontend/src/features/idir/StaffDashboard.tsx | 14 + .../components/IDIRPermitSearchResults.tsx | 2 +- .../components/IDIRPermitSearchRowActions.tsx | 9 +- .../table/PermitSearchResultColumnDef.tsx | 10 +- .../manageProfile/apiManager/hooks.ts | 16 +- .../manageVehicles/apiManager/vehiclesAPI.tsx | 20 +- .../dashboard/AddVehicleDashboard.tsx | 4 +- .../dashboard/EditVehicleDashboard.tsx | 2 +- .../dashboard/ManageVehiclesDashboard.tsx | 9 +- .../components/form/PowerUnitForm.tsx | 2 +- .../components/form/TrailerForm.tsx | 2 +- .../components/form/tests/helpers/prepare.tsx | 2 +- .../manageVehicles/components/list/List.tsx | 2 +- .../manageVehicles/hooks/powerUnits.ts | 4 +- .../manageVehicles/hooks/queryKeys.ts | 6 +- .../features/manageVehicles/hooks/trailers.ts | 4 +- .../features/manageVehicles/hooks/vehicles.ts | 5 +- .../src/features/permits/apiManager/cart.ts | 8 +- .../permits/apiManager/endpoints/endpoints.ts | 46 ++- .../features/permits/apiManager/permitsAPI.ts | 235 ++++++------- .../dashboard/ApplicationStepPage.tsx | 29 +- .../components/dashboard/PermitLists.tsx | 11 +- .../tests/integration/helpers/prepare.tsx | 2 +- .../components/form/ApplicationDetails.tsx | 5 +- .../features/permits/components/list/List.tsx | 216 ------------ .../ApplicationInReviewStatusChip.tsx | 2 +- .../ApplicationsInProgressList.tsx | 12 +- .../permit-list/ApplicationsInReviewList.tsx | 10 +- .../ApplicationsInReviewRowOptions.tsx | 29 +- .../components/permit-list/BasePermitList.tsx | 18 +- .../components/permit-list/Columns.tsx | 10 +- .../components/permit-list/PermitChip.tsx | 2 +- .../permit-list/PermitRowOptions.tsx | 4 +- .../permits/context/CartContextProvider.tsx | 4 +- .../permits/helpers/permitPDFHelper.ts | 27 +- frontend/src/features/permits/hooks/cart.ts | 31 +- frontend/src/features/permits/hooks/hooks.ts | 326 +++++++----------- .../hooks/usePermitVehicleManagement.ts | 2 +- .../permits/hooks/useTableControls.ts | 34 ++ .../permits/pages/Amend/AmendPermit.tsx | 20 +- .../Amend/components/AmendPermitFinish.tsx | 14 +- .../Amend/components/AmendPermitForm.tsx | 4 +- .../Amend/components/AmendPermitReview.tsx | 54 +-- .../pages/Application/ApplicationForm.tsx | 15 +- .../pages/Application/ApplicationReview.tsx | 73 ++-- .../review/ConfirmationCheckboxes.scss | 11 +- .../review/ConfirmationCheckboxes.tsx | 71 ++-- .../components/review/PermitReview.tsx | 31 +- .../components/review/ReviewActions.scss | 25 +- .../components/review/ReviewActions.tsx | 45 ++- .../components/review/ReviewFeeSummary.tsx | 28 +- .../helpers/ApplicationReview/prepare.tsx | 2 +- .../permits/pages/Payment/PaymentRedirect.tsx | 35 +- .../pages/ShoppingCart/ShoppingCartPage.tsx | 39 ++- .../hooks/useCheckOutdatedCart.ts | 3 +- .../ShoppingCart/hooks/useShoppingCart.ts | 2 +- .../permits/pages/Void/FinishVoid.tsx | 16 +- .../permits/pages/Void/VoidPermit.tsx | 10 +- .../pages/Void/components/VoidPermitForm.tsx | 17 +- .../permits/types/PermitReviewContext.ts | 8 + .../src/features/permits/types/application.ts | 15 +- .../queue/apiManager/endpoints/endpoints.ts | 11 + .../src/features/queue/apiManager/queueAPI.ts | 106 ++++++ .../ApplicationInQueueColumnDefinition.tsx | 79 +++++ .../components/ApplicationInQueueReview.scss | 8 + .../components/ApplicationInQueueReview.tsx | 152 ++++++++ .../components/ApplicationQueueLists.tsx | 41 +++ .../components/ApplicationsInQueueList.tsx | 186 ++++++++++ .../components/ClaimedApplicationModal.scss | 74 ++++ .../components/ClaimedApplicationModal.tsx | 78 +++++ .../components/ClaimedApplicationsList.tsx | 209 +++++++++++ .../queue/components/QueueBreadcrumb.tsx | 42 +++ ...imedApplicationInQueueColumnDefinition.tsx | 86 +++++ .../queue/helpers/canViewApplicationQueue.ts | 14 + frontend/src/features/queue/hooks/hooks.ts | 244 +++++++++++++ .../queue/pages/ReviewApplicationInQueue.tsx | 61 ++++ .../types/ApplicationQueueStatus.ts | 0 .../types/CaseActivityType.ts | 0 .../CloseCreditAccountModal.scss | 2 +- .../creditAccount/RemoveUsersModal.scss | 2 +- .../LOA/vehicles/LOADesignateVehicles.tsx | 11 +- frontend/src/routes/Routes.tsx | 36 +- frontend/src/routes/constants.ts | 58 +++- frontend/src/themes/orbcStyles.scss | 1 + 88 files changed, 2375 insertions(+), 891 deletions(-) create mode 100644 frontend/src/features/idir/StaffDashboard.tsx delete mode 100644 frontend/src/features/permits/components/list/List.tsx create mode 100644 frontend/src/features/permits/hooks/useTableControls.ts create mode 100644 frontend/src/features/permits/types/PermitReviewContext.ts create mode 100644 frontend/src/features/queue/apiManager/endpoints/endpoints.ts create mode 100644 frontend/src/features/queue/apiManager/queueAPI.ts create mode 100644 frontend/src/features/queue/components/ApplicationInQueueColumnDefinition.tsx create mode 100644 frontend/src/features/queue/components/ApplicationInQueueReview.scss create mode 100644 frontend/src/features/queue/components/ApplicationInQueueReview.tsx create mode 100644 frontend/src/features/queue/components/ApplicationQueueLists.tsx create mode 100644 frontend/src/features/queue/components/ApplicationsInQueueList.tsx create mode 100644 frontend/src/features/queue/components/ClaimedApplicationModal.scss create mode 100644 frontend/src/features/queue/components/ClaimedApplicationModal.tsx create mode 100644 frontend/src/features/queue/components/ClaimedApplicationsList.tsx create mode 100644 frontend/src/features/queue/components/QueueBreadcrumb.tsx create mode 100644 frontend/src/features/queue/components/UnclaimedApplicationInQueueColumnDefinition.tsx create mode 100644 frontend/src/features/queue/helpers/canViewApplicationQueue.ts create mode 100644 frontend/src/features/queue/hooks/hooks.ts create mode 100644 frontend/src/features/queue/pages/ReviewApplicationInQueue.tsx rename frontend/src/features/{permits => queue}/types/ApplicationQueueStatus.ts (100%) rename frontend/src/features/{permits => queue}/types/CaseActivityType.ts (100%) diff --git a/frontend/src/common/authentication/LoginRedirect.tsx b/frontend/src/common/authentication/LoginRedirect.tsx index 0c8a99329..5b4358f9b 100644 --- a/frontend/src/common/authentication/LoginRedirect.tsx +++ b/frontend/src/common/authentication/LoginRedirect.tsx @@ -18,6 +18,8 @@ import { useUserContext, useUserContextQuery, } from "../../features/manageProfile/apiManager/hooks"; +import { canViewApplicationQueue } from "../../features/queue/helpers/canViewApplicationQueue"; +import { getDefaultRequiredVal } from "../helpers/util"; const navigateBCeID = ( userContextData: BCeIDUserContextType, @@ -25,8 +27,14 @@ const navigateBCeID = ( const { associatedCompanies, pendingCompanies, migratedClient, user } = userContextData; - const isAssociatedSuspended = associatedCompanies?.find((company) => company?.isSuspended); - const isPendingSuspended = pendingCompanies?.find((company) => company?.isSuspended); + const isAssociatedSuspended = getDefaultRequiredVal( + [], + associatedCompanies, + ).find((company) => Boolean(company.isSuspended)); + + const isPendingSuspended = getDefaultRequiredVal([], pendingCompanies).find( + (company) => Boolean(company.isSuspended), + ); // If the user does not exist if (!user?.userGUID) { @@ -109,7 +117,10 @@ export const LoginRedirect = () => { } else if (userFromToken?.profile?.identity_provider === IDPS.IDIR) { const userContextData: Optional = queryClient.getQueryData(["userContext"]); - if (userContextData?.user?.userGUID) { + // only IDIR users with PC, SA, CTPO or TRAIN should redirect to STAFF_HOME + if (canViewApplicationQueue(userContextData?.user?.userRole)) { + navigate(IDIR_ROUTES.STAFF_HOME); + } else if (userContextData?.user?.userGUID) { navigate(IDIR_ROUTES.WELCOME); } else { navigate(ERROR_ROUTES.UNAUTHORIZED); diff --git a/frontend/src/common/authentication/PermissionMatrix.ts b/frontend/src/common/authentication/PermissionMatrix.ts index 607be52d4..093895385 100644 --- a/frontend/src/common/authentication/PermissionMatrix.ts +++ b/frontend/src/common/authentication/PermissionMatrix.ts @@ -312,7 +312,7 @@ const MANAGE_SETTINGS = { /** * View Credit Account tab - Non-Holder/user * Comment: Info box - * + * * Todo: ORV2-2771 Implement info box. */ VIEW_CREDIT_ACCOUNT_TAB_NON_HOLDER_OR_USER: { @@ -410,8 +410,8 @@ const GLOBAL_SEARCH = { * Application review queue on staff home screen */ const STAFF_HOME_SCREEN = { - VIEW_QUEUE: { allowedIDIRRoles: [PC, SA] }, - MANAGE_QUEUE: { allowedIDIRRoles: [PC, SA] }, + VIEW_QUEUE: { allowedIDIRRoles: [PC, SA, CTPO] }, + MANAGE_QUEUE: { allowedIDIRRoles: [PC, SA, CTPO] }, } as const; const MISCELLANEOUS = { diff --git a/frontend/src/common/components/naviconsidebar/NavIconHomeButton.tsx b/frontend/src/common/components/naviconsidebar/NavIconHomeButton.tsx index 0f3baf666..5334802ff 100644 --- a/frontend/src/common/components/naviconsidebar/NavIconHomeButton.tsx +++ b/frontend/src/common/components/naviconsidebar/NavIconHomeButton.tsx @@ -5,6 +5,7 @@ import { IDIR_ROUTES } from "../../../routes/constants"; import OnRouteBCContext from "../../authentication/OnRouteBCContext"; import { NavButton } from "./NavButton"; import { NAV_BUTTON_TYPES } from "./types/NavButtonType"; +import { canViewApplicationQueue } from "../../../features/queue/helpers/canViewApplicationQueue"; /** * Displays the navigation icon for Home on the NavIconSideBar @@ -13,14 +14,18 @@ export const NavIconHomeButton = () => { const navigate = useNavigate(); const { pathname } = useLocation(); const isActive = pathname === IDIR_ROUTES.WELCOME; - const { clearCompanyContext } = useContext(OnRouteBCContext); + const { clearCompanyContext, idirUserDetails } = useContext(OnRouteBCContext); return ( { clearCompanyContext?.(); - navigate(IDIR_ROUTES.WELCOME); + navigate( + canViewApplicationQueue(idirUserDetails?.userRole) + ? IDIR_ROUTES.STAFF_HOME + : IDIR_ROUTES.WELCOME, + ); }} isActive={isActive} /> diff --git a/frontend/src/common/types/common.ts b/frontend/src/common/types/common.ts index 585a0803b..5e90d2636 100644 --- a/frontend/src/common/types/common.ts +++ b/frontend/src/common/types/common.ts @@ -49,7 +49,7 @@ export interface PaginationOptions { * Max. value is 25. */ take: number; -}; +} /** * The sort directions. @@ -75,7 +75,7 @@ export interface SortingConfig { * If not given a value, defaulted to false. */ descending?: boolean; -}; +} /** * Additional data filters that could be used for @@ -86,16 +86,23 @@ export interface DataFilterOptions { * The search value entered by the user. */ searchString?: string; + /** + * The column to which the searchString will be applied. + */ + // TODO create a type for the searchColumn which provides "applicationNumber" & "plate" and ensure that a default value is passed where necessary + searchColumn?: string; /** * The sorting configuration selected by the user. */ orderBy?: Array; -}; +} /** * The options for pagination and filtering data. */ -export interface PaginationAndFilters extends PaginationOptions, DataFilterOptions {}; +export interface PaginationAndFilters + extends PaginationOptions, + DataFilterOptions {} /** * The metadata containing info about a page in the paginated response. @@ -117,7 +124,7 @@ export interface PageMetadataInResponse extends PaginationOptions { * Is there a next page? */ hasNextPage: boolean; -}; +} /** * A generic paginated response structure for all the paginated responses from APIs. @@ -131,7 +138,7 @@ export interface PaginatedResponse { * Metadata about a page. */ meta: PageMetadataInResponse; -}; +} export type Optional = T | undefined; export type RequiredOrNull = T | null; diff --git a/frontend/src/features/idir/StaffDashboard.tsx b/frontend/src/features/idir/StaffDashboard.tsx new file mode 100644 index 000000000..f09d085a2 --- /dev/null +++ b/frontend/src/features/idir/StaffDashboard.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { ErrorBoundary } from "react-error-boundary"; +import { ApplicationQueueLists } from "../queue/components/ApplicationQueueLists"; +import { ErrorFallback } from "../../common/pages/ErrorFallback"; + +export const StaffDashboard = React.memo(() => { + return ( + + + + ); +}); + +StaffDashboard.displayName = "StaffDashboard"; diff --git a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx index a3a23cc0f..5444089b0 100644 --- a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx +++ b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx @@ -184,7 +184,7 @@ export const IDIRPermitSearchResults = memo( permitNumber={row.original.permitNumber} permitId={row.original.permitId} userRole={idirUserDetails?.userRole} - companyId={row.original.companyId?.toString()} + companyId={row.original.companyId} />
); diff --git a/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx b/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx index fb0203822..370c1c2d2 100644 --- a/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx +++ b/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx @@ -113,8 +113,7 @@ export const IDIRPermitSearchRowActions = ({ * The role for the current user (eg. PPCCLERK or EOFFICER) */ userRole?: string; - - companyId?: string; + companyId: number; }) => { const [openResendDialog, setOpenResendDialog] = useState(false); const navigate = useNavigate(); @@ -129,7 +128,11 @@ export const IDIRPermitSearchRowActions = ({ if (selectedOption === PERMIT_ACTION_TYPES.RESEND) { setOpenResendDialog(() => true); } else if (selectedOption === PERMIT_ACTION_TYPES.VIEW_RECEIPT) { - viewReceiptPdf(permitId, () => navigate(routes.ERROR_ROUTES.DOCUMENT_UNAVAILABLE), companyId ); + viewReceiptPdf( + companyId, + permitId, + () => navigate(routes.ERROR_ROUTES.DOCUMENT_UNAVAILABLE), + ); } else if (selectedOption === PERMIT_ACTION_TYPES.VOID_REVOKE) { navigate(`${routes.PERMITS_ROUTES.VOID(companyId, permitId)}`); } else if (selectedOption === PERMIT_ACTION_TYPES.AMEND) { diff --git a/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx b/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx index 0358d69e0..881823ccc 100644 --- a/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx +++ b/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx @@ -13,7 +13,9 @@ import { formatCellValuetoDatetime, } from "../../../../common/helpers/tableHelper"; -export const PermitSearchResultColumnDef = (onDocumentUnavailable: () => void): MRT_ColumnDef[] => [ +export const PermitSearchResultColumnDef = ( + onDocumentUnavailable: () => void, +): MRT_ColumnDef[] => [ { accessorKey: "permitNumber", header: "Permit #", @@ -27,7 +29,11 @@ export const PermitSearchResultColumnDef = (onDocumentUnavailable: () => void): <> { - viewPermitPdf(permitId.toString(), () => onDocumentUnavailable(), companyId.toString()); + viewPermitPdf( + companyId, + permitId, + () => onDocumentUnavailable(), + ); } } > diff --git a/frontend/src/features/manageProfile/apiManager/hooks.ts b/frontend/src/features/manageProfile/apiManager/hooks.ts index 4a62a327a..25f874dc3 100644 --- a/frontend/src/features/manageProfile/apiManager/hooks.ts +++ b/frontend/src/features/manageProfile/apiManager/hooks.ts @@ -8,8 +8,6 @@ import { IDPS } from "../../../common/types/idp"; import { Nullable } from "../../../common/types/common"; import { ERROR_ROUTES } from "../../../routes/constants"; import { DeleteResponse } from "../types/manageProfile"; -import { getCompanyIdFromSession } from "../../../common/apiManager/httpRequestHandler"; -import { getDefaultRequiredVal } from "../../../common/helpers/util"; import { FIVE_MINUTES, FOUR_MINUTES, @@ -39,7 +37,7 @@ import { /** * Fetches company info of current user. - * @returns company info of current user, or error if failed + * @returns Query object containing company info of current user */ export const useCompanyInfoQuery = () => { return useQuery({ @@ -53,19 +51,15 @@ export const useCompanyInfoQuery = () => { /** * Fetches company info of specific company. - * @returns company info or error if failed + * @param companyId Id of the company to get the info for + * @returns Query object containing company info */ export const useCompanyInfoDetailsQuery = ( - companyIdParam?: Nullable, + companyId: number, ) => { - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); return useQuery({ queryKey: ["companyInfo"], - queryFn: () => getCompanyInfoById(Number(companyId)), + queryFn: () => getCompanyInfoById(companyId), enabled: Boolean(companyId), refetchInterval: FIVE_MINUTES, refetchOnWindowFocus: false, // fixes issue where a query is run everytime the screen is brought to foreground diff --git a/frontend/src/features/manageVehicles/apiManager/vehiclesAPI.tsx b/frontend/src/features/manageVehicles/apiManager/vehiclesAPI.tsx index 2cbbe1f92..028691e03 100644 --- a/frontend/src/features/manageVehicles/apiManager/vehiclesAPI.tsx +++ b/frontend/src/features/manageVehicles/apiManager/vehiclesAPI.tsx @@ -30,7 +30,7 @@ const emptyUnitNumberToNull = (unitNumber?: Nullable) => { * @return Response of all power units */ export const getAllPowerUnits = async ( - companyId: string, + companyId: number, ): Promise => { const url = VEHICLES_API.POWER_UNITS.ALL(companyId); return httpGETRequest(url).then((response) => response.data); @@ -44,7 +44,7 @@ export const getAllPowerUnits = async ( */ export const getPowerUnit = async ( powerUnitId: string, - companyId: string, + companyId: number, ): Promise => { const url = VEHICLES_API.POWER_UNITS.DETAIL(companyId, powerUnitId); return httpGETRequest(url).then((response) => response.data); @@ -69,7 +69,7 @@ export const addPowerUnit = async ({ companyId, powerUnit, }: { - companyId: string | number; + companyId: number; powerUnit: PowerUnitCreateData; }) => { const url = VEHICLES_API.POWER_UNITS.ADD(companyId); @@ -95,7 +95,7 @@ export const updatePowerUnit = async ({ powerUnit, powerUnitId, }: { - companyId: string; + companyId: number; powerUnit: PowerUnitUpdateData; powerUnitId: string; }) => { @@ -114,7 +114,7 @@ export const updatePowerUnit = async ({ * @param companyId Id of the company to fetch for. * @return Response of all trailers */ -export const getAllTrailers = async (companyId: string): Promise => { +export const getAllTrailers = async (companyId: number): Promise => { const url = VEHICLES_API.TRAILERS.ALL(companyId); return httpGETRequest(url).then((response) => response.data); }; @@ -127,7 +127,7 @@ export const getAllTrailers = async (companyId: string): Promise => { */ export const getTrailer = async ( trailerId: string, - companyId: string, + companyId: number, ): Promise => { const url = VEHICLES_API.TRAILERS.DETAIL(companyId, trailerId); return httpGETRequest(url).then((response) => response.data); @@ -152,7 +152,7 @@ export const addTrailer = async ({ companyId, trailer, }: { - companyId: string; + companyId: number; trailer: TrailerCreateData; }) => { const url = VEHICLES_API.TRAILERS.ADD(companyId); @@ -176,7 +176,7 @@ export const updateTrailer = async ({ trailerId, trailer, }: { - companyId: string; + companyId: number; trailerId: string; trailer: TrailerUpdateData; }) => { @@ -197,7 +197,7 @@ export const updateTrailer = async ({ */ export const deletePowerUnits = async ( vehicleIds: string[], - companyId: string, + companyId: number, ) => { const url = VEHICLES_API.POWER_UNITS.DELETE(companyId); return await httpPOSTRequest(url, replaceEmptyValuesWithNull({ powerUnits: vehicleIds })); @@ -211,7 +211,7 @@ export const deletePowerUnits = async ( */ export const deleteTrailers = async ( vehicleIds: string[], - companyId: string, + companyId: number, ) => { const url = VEHICLES_API.TRAILERS.DELETE(companyId); return await httpPOSTRequest(url, replaceEmptyValuesWithNull({ trailers: vehicleIds })); diff --git a/frontend/src/features/manageVehicles/components/dashboard/AddVehicleDashboard.tsx b/frontend/src/features/manageVehicles/components/dashboard/AddVehicleDashboard.tsx index 00f670e41..a78c73c05 100644 --- a/frontend/src/features/manageVehicles/components/dashboard/AddVehicleDashboard.tsx +++ b/frontend/src/features/manageVehicles/components/dashboard/AddVehicleDashboard.tsx @@ -12,13 +12,13 @@ import { TrailerForm } from "../form/TrailerForm"; import { VEHICLES_ROUTES } from "../../../../routes/constants"; import { BANNER_MESSAGES } from "../../../../common/constants/bannerMessages"; import { VEHICLE_TYPES, VehicleType } from "../../types/Vehicle"; -import { getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { applyWhenNotNullable } from "../../../../common/helpers/util"; import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler"; export const AddVehicleDashboard = React.memo( ({ vehicleType }: { vehicleType: VehicleType }) => { const navigate = useNavigate(); - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); + const companyId: number = applyWhenNotNullable(id => Number(id), getCompanyIdFromSession(), 0); const isTrailer = vehicleType === VEHICLE_TYPES.TRAILER; const backToVehicleInventory = () => { diff --git a/frontend/src/features/manageVehicles/components/dashboard/EditVehicleDashboard.tsx b/frontend/src/features/manageVehicles/components/dashboard/EditVehicleDashboard.tsx index a32cf5846..fc8f65e62 100644 --- a/frontend/src/features/manageVehicles/components/dashboard/EditVehicleDashboard.tsx +++ b/frontend/src/features/manageVehicles/components/dashboard/EditVehicleDashboard.tsx @@ -31,7 +31,7 @@ export const EditVehicleDashboard = React.memo( ({ vehicleType }: { vehicleType: VehicleType }) => { const navigate = useNavigate(); const { vehicleId } = useParams(); - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); + const companyId: number = applyWhenNotNullable(id => Number(id), getCompanyIdFromSession(), 0); const isTrailer = vehicleType === VEHICLE_TYPES.TRAILER; const { data: vehicleToEdit, isError } = useVehicleByIdQuery( diff --git a/frontend/src/features/manageVehicles/components/dashboard/ManageVehiclesDashboard.tsx b/frontend/src/features/manageVehicles/components/dashboard/ManageVehiclesDashboard.tsx index e57f5ee90..7c7e6a8c0 100644 --- a/frontend/src/features/manageVehicles/components/dashboard/ManageVehiclesDashboard.tsx +++ b/frontend/src/features/manageVehicles/components/dashboard/ManageVehiclesDashboard.tsx @@ -8,7 +8,7 @@ import { List } from "../list/List"; import { DoesUserHaveClaimWithContext } from "../../../../common/authentication/util"; import { CLAIMS } from "../../../../common/authentication/types"; import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler"; -import { getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { applyWhenNotNullable } from "../../../../common/helpers/util"; import { VEHICLES_DASHBOARD_TABS } from "../../../../routes/constants"; import { VEHICLE_TYPES } from "../../types/Vehicle"; import { usePowerUnitsQuery } from "../../hooks/powerUnits"; @@ -35,7 +35,12 @@ const useTabIndexFromURL = (): number => { * React component to render the vehicle inventory */ export const ManageVehiclesDashboard = memo(() => { - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); + const companyId: number = applyWhenNotNullable( + id => Number(id), + getCompanyIdFromSession(), + 0, + ); + const staleTime = 5000; const powerUnitsQuery = usePowerUnitsQuery(companyId, staleTime); const trailersQuery = useTrailersQuery(companyId, staleTime); diff --git a/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx b/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx index ef1b97c4e..9515bd2be 100644 --- a/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx +++ b/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx @@ -36,7 +36,7 @@ export const PowerUnitForm = ({ companyId, powerUnit, }: { - companyId: string; + companyId: number; powerUnit?: PowerUnit; }) => { const isEditMode = Boolean(powerUnit?.powerUnitId); diff --git a/frontend/src/features/manageVehicles/components/form/TrailerForm.tsx b/frontend/src/features/manageVehicles/components/form/TrailerForm.tsx index f8e18b080..6541cf183 100644 --- a/frontend/src/features/manageVehicles/components/form/TrailerForm.tsx +++ b/frontend/src/features/manageVehicles/components/form/TrailerForm.tsx @@ -36,7 +36,7 @@ export const TrailerForm = ({ companyId, trailer, }: { - companyId: string; + companyId: number; trailer?: Trailer; }) => { const isEditMode = Boolean(trailer?.trailerId); diff --git a/frontend/src/features/manageVehicles/components/form/tests/helpers/prepare.tsx b/frontend/src/features/manageVehicles/components/form/tests/helpers/prepare.tsx index 9b81c0389..0dacbb250 100644 --- a/frontend/src/features/manageVehicles/components/form/tests/helpers/prepare.tsx +++ b/frontend/src/features/manageVehicles/components/form/tests/helpers/prepare.tsx @@ -28,7 +28,7 @@ export const defaultTrailerSubtypes = [ }, ]; -const companyId = `${getDefaultCompanyInfo().companyId}`; +const companyId = getDefaultCompanyInfo().companyId; const server = setupServer( // Mock getting power unit types diff --git a/frontend/src/features/manageVehicles/components/list/List.tsx b/frontend/src/features/manageVehicles/components/list/List.tsx index 51fff3a8f..2bc3520b0 100644 --- a/frontend/src/features/manageVehicles/components/list/List.tsx +++ b/frontend/src/features/manageVehicles/components/list/List.tsx @@ -80,7 +80,7 @@ export const List = memo( }: { vehicleType: VehicleType; query: UseQueryResult; - companyId: string; + companyId: number; }) => { const navigate = useNavigate(); const { diff --git a/frontend/src/features/manageVehicles/hooks/powerUnits.ts b/frontend/src/features/manageVehicles/hooks/powerUnits.ts index 77af580cb..9659780b3 100644 --- a/frontend/src/features/manageVehicles/hooks/powerUnits.ts +++ b/frontend/src/features/manageVehicles/hooks/powerUnits.ts @@ -34,7 +34,7 @@ export const usePowerUnitSubTypesQuery = () => { * @returns Query object for fetching power units */ export const usePowerUnitsQuery = ( - companyId: string, + companyId: number, staleTime?: number, ) => { return useQuery({ @@ -100,7 +100,7 @@ export const useDeletePowerUnitsMutation = () => { companyId, vehicleIds, }: { - companyId: string; + companyId: number; vehicleIds: string[]; }) => deletePowerUnits(vehicleIds, companyId), onSuccess: (response) => { diff --git a/frontend/src/features/manageVehicles/hooks/queryKeys.ts b/frontend/src/features/manageVehicles/hooks/queryKeys.ts index 9d1ee8fbe..1c7badd2d 100644 --- a/frontend/src/features/manageVehicles/hooks/queryKeys.ts +++ b/frontend/src/features/manageVehicles/hooks/queryKeys.ts @@ -1,10 +1,12 @@ +import { Nullable } from "../../../common/types/common"; + export const QUERY_KEYS = { POWER_UNIT_SUBTYPES: () => ["powerUnitSubTypes"], POWER_UNITS: () => ["powerUnits"], - POWER_UNIT: (companyId: string, vehicleId?: string) => + POWER_UNIT: (companyId: string | number, vehicleId?: Nullable) => ["powerUnit", vehicleId, companyId], TRAILER_SUBTYPES: () => ["trailerSubTypes"], TRAILERS: () => ["trailers"], - TRAILER: (companyId: string, vehicleId?: string) => + TRAILER: (companyId: string | number, vehicleId?: Nullable) => ["trailer", vehicleId, companyId], }; diff --git a/frontend/src/features/manageVehicles/hooks/trailers.ts b/frontend/src/features/manageVehicles/hooks/trailers.ts index dffcba996..e3f83f829 100644 --- a/frontend/src/features/manageVehicles/hooks/trailers.ts +++ b/frontend/src/features/manageVehicles/hooks/trailers.ts @@ -29,7 +29,7 @@ export const useTrailerSubTypesQuery = () => { * @returns Query object for fetching trailers */ export const useTrailersQuery = ( - companyId: string, + companyId: number, staleTime?: number, ) => { return useQuery({ @@ -95,7 +95,7 @@ export const useDeleteTrailersMutation = () => { companyId, vehicleIds, }: { - companyId: string; + companyId: number; vehicleIds: string[]; }) => deleteTrailers(vehicleIds, companyId), onSuccess: (response) => { diff --git a/frontend/src/features/manageVehicles/hooks/vehicles.ts b/frontend/src/features/manageVehicles/hooks/vehicles.ts index 500a0a218..0bae7ff3c 100644 --- a/frontend/src/features/manageVehicles/hooks/vehicles.ts +++ b/frontend/src/features/manageVehicles/hooks/vehicles.ts @@ -4,6 +4,7 @@ import { VEHICLE_TYPES, VehicleType } from "../types/Vehicle"; import { QUERY_KEYS } from "./queryKeys"; import { getPowerUnit, getTrailer } from "../apiManager/vehiclesAPI"; import { getDefaultRequiredVal } from "../../../common/helpers/util"; +import { Nullable } from "../../../common/types/common"; /** * Hook that fetches vehicle details for a company. @@ -13,9 +14,9 @@ import { getDefaultRequiredVal } from "../../../common/helpers/util"; * @returns Query Results */ export const useVehicleByIdQuery = ( - companyId: string, + companyId: number, vehicleType: VehicleType, - vehicleId?: string, + vehicleId?: Nullable, ) => { const queryKey = vehicleType === VEHICLE_TYPES.POWER_UNIT ? QUERY_KEYS.POWER_UNIT(companyId, vehicleId) diff --git a/frontend/src/features/permits/apiManager/cart.ts b/frontend/src/features/permits/apiManager/cart.ts index c2709f8d5..ed163af18 100644 --- a/frontend/src/features/permits/apiManager/cart.ts +++ b/frontend/src/features/permits/apiManager/cart.ts @@ -9,7 +9,7 @@ import { CART_API_ROUTES } from "./endpoints/endpoints"; * @returns Response from backend containing application ids that were added successfully or failed */ export const addToCart = async ( - companyId: string, + companyId: number, applicationIds: string[], ): Promise => { const response = await httpPOSTRequest(CART_API_ROUTES.ADD(companyId), { @@ -26,7 +26,7 @@ export const addToCart = async ( * @returns Response from backend with applications in the cart belonging to the company */ export const fetchCart = async ( - companyId: string, + companyId: number, fetchAllApplications?: boolean, ): Promise => { const response = await httpGETRequest(CART_API_ROUTES.GET(companyId, fetchAllApplications)); @@ -40,7 +40,7 @@ export const fetchCart = async ( * @returns Response from backend containing application ids that were successfully removed from cart or failed */ export const removeFromCart = async ( - companyId: string, + companyId: number, applicationIds: string[], ): Promise => { const response = await httpDELETERequest(CART_API_ROUTES.REMOVE(companyId), { @@ -56,7 +56,7 @@ export const removeFromCart = async ( * @returns Number of items currently in the cart */ export const getCartCount = async ( - companyId: string, + companyId: number, ): Promise => { const response = await httpGETRequest(CART_API_ROUTES.COUNT(companyId)); return response.data; diff --git a/frontend/src/features/permits/apiManager/endpoints/endpoints.ts b/frontend/src/features/permits/apiManager/endpoints/endpoints.ts index b5aa881cd..8716fc866 100644 --- a/frontend/src/features/permits/apiManager/endpoints/endpoints.ts +++ b/frontend/src/features/permits/apiManager/endpoints/endpoints.ts @@ -1,33 +1,41 @@ import { VEHICLES_URL } from "../../../../common/apiManager/endpoints/endpoints"; -const APPLICATIONS_API_BASE = (companyId: string) => +const APPLICATIONS_API_BASE = (companyId: string | number) => `${VEHICLES_URL}/companies/${companyId}/applications`; -const PERMITS_API_BASE = (companyId: string) => +const PERMITS_API_BASE = (companyId: string | number) => `${VEHICLES_URL}/companies/${companyId}/permits`; const STAFF_PERMIT_API_BASE = `${VEHICLES_URL}/permits`; +const STAFF_APPLICATIONS_API_BASE = `${VEHICLES_URL}/applications`; + export const APPLICATIONS_API_ROUTES = { - CREATE: (companyId: string) => APPLICATIONS_API_BASE(companyId), - UPDATE: (companyId: string) => APPLICATIONS_API_BASE(companyId), - GET: (companyId: string) => APPLICATIONS_API_BASE(companyId), - DELETE: (companyId: string) => APPLICATIONS_API_BASE(companyId), + CREATE: (companyId: string | number) => APPLICATIONS_API_BASE(companyId), + UPDATE: ( + companyId: string | number, + permitId: string, + ) => `${APPLICATIONS_API_BASE(companyId)}/${permitId}`, + GET: ( + companyId: string | number, + permitId: string, + ) => `${APPLICATIONS_API_BASE(companyId)}/${permitId}`, + GET_APPLICATIONS: (companyId: string | number) => APPLICATIONS_API_BASE(companyId), + DELETE: (companyId: string | number) => APPLICATIONS_API_BASE(companyId), }; -export const APPLICATION_QUEUE_API_ROUTES = { - UPDATE_QUEUE_STATUS: (companyId: string, applicationId: string) => - `${APPLICATIONS_API_BASE(companyId)}/${applicationId}/queue/status`, +export const STAFF_APPLICATIONS_API_ROUTES = { + GET: () => STAFF_APPLICATIONS_API_BASE, }; export const PERMITS_API_ROUTES = { - BASE: (companyId: string) => PERMITS_API_BASE(companyId), - GET: (companyId: string) => PERMITS_API_BASE(companyId), - ISSUE: (companyId: string) => `${APPLICATIONS_API_BASE(companyId)}/issue`, + BASE: (companyId: string | number) => PERMITS_API_BASE(companyId), + GET: (companyId: string | number) => PERMITS_API_BASE(companyId), + ISSUE: (companyId: string | number) => `${APPLICATIONS_API_BASE(companyId)}/issue`, AMEND: APPLICATIONS_API_ROUTES.CREATE, - DOWNLOAD: (companyId: string, permitId: string) => + DOWNLOAD: (companyId: string | number, permitId: string) => `${PERMITS_API_BASE(companyId)}/${permitId}/document`, - RECEIPT: (companyId: string, permitId: string) => + RECEIPT: (companyId: string | number, permitId: string) => `${PERMITS_API_BASE(companyId)}/${permitId}/receipt`, VOID: (permitId: string) => `${STAFF_PERMIT_API_BASE}/${permitId}/void`, RESEND: (permitId: string) => @@ -43,18 +51,18 @@ export const PAYMENT_API_ROUTES = { PAYMENT_GATEWAY: `payment-gateway`, }; -const CART_API_BASE = (companyId: string) => +const CART_API_BASE = (companyId: string | number) => `${VEHICLES_URL}/companies/${companyId}/shopping-cart`; export const CART_API_ROUTES = { - GET: (companyId: string, fetchAllApplications?: boolean) => { + GET: (companyId: string | number, fetchAllApplications?: boolean) => { if (typeof fetchAllApplications === "undefined") { return CART_API_BASE(companyId); } return `${CART_API_BASE(companyId)}?allApplications=${Boolean(fetchAllApplications)}`; }, - ADD: (companyId: string) => CART_API_BASE(companyId), - REMOVE: (companyId: string) => CART_API_BASE(companyId), - COUNT: (companyId: string) => `${CART_API_BASE(companyId)}/count`, + ADD: (companyId: string | number) => CART_API_BASE(companyId), + REMOVE: (companyId: string | number) => CART_API_BASE(companyId), + COUNT: (companyId: string | number) => `${CART_API_BASE(companyId)}/count`, }; diff --git a/frontend/src/features/permits/apiManager/permitsAPI.ts b/frontend/src/features/permits/apiManager/permitsAPI.ts index dd9d84127..52ac62117 100644 --- a/frontend/src/features/permits/apiManager/permitsAPI.ts +++ b/frontend/src/features/permits/apiManager/permitsAPI.ts @@ -4,6 +4,8 @@ import { PermitHistory } from "../types/PermitHistory"; import { removeEmptyIdsFromPermitsActionResponse } from "../helpers/mappers"; import { AmendPermitFormData } from "../pages/Amend/types/AmendPermitFormData"; import { DATE_FORMATS, toLocal } from "../../../common/helpers/formatDate"; +import { EmailNotificationType } from "../types/EmailNotificationType"; +import { APPLICATION_QUEUE_STATUSES } from "../../queue/types/ApplicationQueueStatus"; import { IssuePermitsResponse, PermitListItem, @@ -30,7 +32,6 @@ import { } from "../types/payment"; import { - getCompanyIdFromSession, httpGETRequest, httpPUTRequest, httpPOSTRequest, @@ -49,13 +50,14 @@ import { ApplicationResponseData, ApplicationListItem, ApplicationFormData, + ApplicationFilters, } from "../types/application"; import { - APPLICATION_QUEUE_API_ROUTES, APPLICATIONS_API_ROUTES, PAYMENT_API_ROUTES, PERMITS_API_ROUTES, + STAFF_APPLICATIONS_API_ROUTES, } from "./endpoints/endpoints"; import { @@ -63,8 +65,6 @@ import { VoidPermitRequestData, VoidPermitResponseData, } from "../pages/Void/types/VoidPermit"; -import { EmailNotificationType } from "../types/EmailNotificationType"; -import { CaseActivityType } from "../types/CaseActivityType"; /** * Create a new application. @@ -73,8 +73,8 @@ import { CaseActivityType } from "../types/CaseActivityType"; */ export const createApplication = async ( application: ApplicationFormData, + companyId: number, ): Promise> => { - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); return await httpPOSTRequest( APPLICATIONS_API_ROUTES.CREATE(companyId), replaceEmptyValuesWithNull({ @@ -88,21 +88,16 @@ export const createApplication = async ( * Update an existing application. * @param application application data * @param applicationId application number for the application to update - * @param companyIdParam company id + * @param companyId id of the company that the application belongs to * @returns response with updated application data, or error if failed */ export const updateApplication = async ( application: ApplicationFormData, applicationId: string, - companyIdParam?: Nullable, + companyId: number, ): Promise> => { - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); return await httpPUTRequest( - `${APPLICATIONS_API_ROUTES.UPDATE(companyId)}/${applicationId}`, + APPLICATIONS_API_ROUTES.UPDATE(companyId, applicationId), replaceEmptyValuesWithNull({ // must convert application to ApplicationRequestData (dayjs fields to strings) ...serializeForUpdateApplication(application), @@ -110,18 +105,28 @@ export const updateApplication = async ( ); }; -const getApplications = async ( +export const getApplications = async ( { page = 0, take = 10, searchString = "", orderBy = [], + searchColumn = "", }: PaginationAndFilters, - pendingPermitsOnly?: boolean, - applicationsInQueueOnly?: boolean, + { + pendingPermitsOnly, + applicationsInQueueOnly, + claimedApplicationsOnly, + unclaimedApplicationsOnly, + getStaffQueue, + }: ApplicationFilters, + companyId?: Nullable, ): Promise> => { - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); - const applicationsURL = new URL(APPLICATIONS_API_ROUTES.GET(companyId)); + // If the user is staff and not acting as a company, get timeInQueue and claimedBy properties + // in addition to the ApplicationListItem response to be used in the ApplicationsInQueueList component + const applicationsURL = !getStaffQueue && companyId + ? new URL(APPLICATIONS_API_ROUTES.GET_APPLICATIONS(companyId)) + : new URL(STAFF_APPLICATIONS_API_ROUTES.GET()); // API pagination index starts at 1. Hence page + 1. applicationsURL.searchParams.set("page", `${page + 1}`); @@ -136,8 +141,22 @@ const getApplications = async ( if (typeof applicationsInQueueOnly !== "undefined") { applicationsURL.searchParams.set( - "applicationsInQueue", - `${Boolean(applicationsInQueueOnly)}`, + "applicationQueueStatus", + `${APPLICATION_QUEUE_STATUSES.PENDING_REVIEW},${APPLICATION_QUEUE_STATUSES.IN_REVIEW}`, + ); + } + + if (typeof claimedApplicationsOnly !== "undefined") { + applicationsURL.searchParams.set( + "applicationQueueStatus", + `${APPLICATION_QUEUE_STATUSES.IN_REVIEW}`, + ); + } + + if (typeof unclaimedApplicationsOnly !== "undefined") { + applicationsURL.searchParams.set( + "applicationQueueStatus", + `${APPLICATION_QUEUE_STATUSES.PENDING_REVIEW}`, ); } @@ -149,6 +168,10 @@ const getApplications = async ( applicationsURL.searchParams.set("orderBy", stringifyOrderBy(orderBy)); } + if (searchColumn.length > 0) { + applicationsURL.searchParams.set("searchColumn", searchColumn); + } + const applications = await httpGETRequest(applicationsURL.toString()) .then((response) => { const paginatedResponseObject = getDefaultRequiredVal( @@ -190,9 +213,14 @@ const getApplications = async ( * @return A list of applications in the IN_PROGRESS and WAITING_PAYMENT statuses */ export const getApplicationsInProgress = async ( + companyId: number, paginationFilters: PaginationAndFilters, ): Promise> => { - return await getApplications(paginationFilters, false); + return await getApplications( + paginationFilters, + { pendingPermitsOnly: false }, + companyId, + ); }; /** @@ -200,33 +228,28 @@ export const getApplicationsInProgress = async ( * @return A list of pending permits in the PAYMENT_COMPLETE status */ export const getPendingPermits = async ( + companyId: number, paginationFilters: PaginationAndFilters, ): Promise> => { - return await getApplications(paginationFilters, true); -}; - -/** - * Fetch all applications in queue. - * @return A list of applications in queue (PENDING_REVIEW, IN_REVIEW) - */ -export const getApplicationsInQueue = async ( - paginationFilters: PaginationAndFilters, -): Promise> => { - return await getApplications(paginationFilters, undefined, true); + return await getApplications( + paginationFilters, + { pendingPermitsOnly: true }, + companyId, + ); }; /** * Fetch application by its permit id. + * @param companyId company id of the company who owns the application * @param permitId permit id of the application to fetch * @returns ApplicationResponseData data as response, or null if fetch failed */ -export const getApplicationByPermitId = async ( - permitId?: Nullable, +export const getApplication = async ( + companyId: number, + permitId: string, ): Promise> => { try { - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); - const url = `${APPLICATIONS_API_ROUTES.GET(companyId)}/${permitId}`; - + const url = APPLICATIONS_API_ROUTES.GET(companyId, permitId); const response = await httpGETRequest(url); return response.data; } catch (err) { @@ -236,14 +259,18 @@ export const getApplicationByPermitId = async ( /** * Delete one or more applications. - * @param permitIds Array of permit ids to be deleted. + * @param companyId id of the company to delete the applications from + * @param applicationIds List of permit ids of the applications to be deleted * @returns A Promise with the API response. */ -export const deleteApplications = async (applicationIds: Array) => { +export const deleteApplications = async ( + companyId: number, + applicationIds: string[], +) => { const requestBody = { applications: applicationIds, }; - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); + return await httpDELETERequest( `${APPLICATIONS_API_ROUTES.DELETE(companyId)}`, replaceEmptyValuesWithNull(requestBody), @@ -258,36 +285,28 @@ const streamDownload = async (url: string) => { /** * Download permit application pdf file. - * @param permitId permit id of the permit application. - * @returns A Promise of dms reference string. + * @param companyId id of the company that the application belongs to + * @param permitId permit id of the permit application + * @returns A Promise of dms reference string */ export const downloadPermitApplicationPdf = async ( + companyId: number, permitId: string, - companyIdParam?: string, ) => { - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); const url = PERMITS_API_ROUTES.DOWNLOAD(companyId, permitId); return await streamDownload(url); }; /** * Download permit receipt pdf file. - * @param permitId permit id of the permit application associated with the receipt. - * @returns A Promise of dms reference string. + * @param companyId id of the company that the receipt belongs to + * @param permitId permit id of the permit application associated with the receipt + * @returns A Promise of dms reference string */ export const downloadReceiptPdf = async ( + companyId: number, permitId: string, - companyIdParam?: string, ) => { - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); const url = PERMITS_API_ROUTES.RECEIPT(companyId, permitId); return await streamDownload(url); }; @@ -347,19 +366,15 @@ export const completeTransaction = async (transactionData: { /** * Issues the permits indicated by the application/permit ids. - * @param applicationIds Application/permit ids for the permits to be issued. - * @returns Successful and failed permit ids that were issued. + * @param companyId id of the company to issue permits for + * @param applicationIds Application/permit ids for the permits to be issued + * @returns Successful and failed permit ids that were issued */ export const issuePermits = async ( + companyId: number, applicationIds: string[], - companyIdParam?: Nullable, ): Promise => { try { - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); const response = await httpPOSTRequest( PERMITS_API_ROUTES.ISSUE(companyId), replaceEmptyValuesWithNull({ @@ -387,21 +402,17 @@ export const issuePermits = async ( }; /** - * Get permit by permit id + * Get permit details. + * @param companyId id of the company that the permit belongs to * @param permitId Permit id of the permit to be retrieved. * @returns Permit information if found, or undefined */ export const getPermit = async ( - permitId?: Nullable, - companyIdParam?: Nullable, + companyId: number, + permitId: string, ): Promise> => { - if (!permitId) return null; + if (!companyId || !permitId) return null; - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); const permitsURL = `${PERMITS_API_ROUTES.GET(companyId)}/${permitId}`; const response = await httpGETRequest(permitsURL); @@ -410,22 +421,19 @@ export const getPermit = async ( }; /** - * Get current application for amendment, if there is one - * @param originalId Original permit id of the permit that is amended. - * @returns Permit application information, if any + * Get current application for amendment, if there is one. + * @param companyId id of the company that the original permit belongs to + * @param originalId Original permit id of the permit that is amended + * @returns Current amendment application information, if any */ export const getCurrentAmendmentApplication = async ( - originalId?: Nullable, - companyIdParam?: Nullable, + companyId: number, + originalId: string, ): Promise> => { - if (!originalId) return null; - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); + if (!companyId || !originalId) return null; + const permitsURL = new URL( - `${APPLICATIONS_API_ROUTES.GET(companyId)}/${originalId}`, + APPLICATIONS_API_ROUTES.GET(companyId, originalId), ); permitsURL.searchParams.set("amendment", "true"); @@ -440,15 +448,16 @@ export const getCurrentAmendmentApplication = async ( /** * Retrieve the list of active or expired permits. - * @param expired If set to true, expired permits will be retrieved. - * @param paginationOptions The pagination and filters applied. - * @returns A list of permits. + * @param companyId id of the company to get permits for + * @param expired If set to true, expired permits will be retrieved + * @param paginationOptions The pagination and filters applied + * @returns A list of permits */ export const getPermits = async ( + companyId: number, { expired = false } = {}, { page = 0, take = 10, searchString, orderBy = [] }: PaginationAndFilters, ): Promise> => { - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); const permitsURL = new URL(PERMITS_API_ROUTES.GET(companyId)); permitsURL.searchParams.set("expired", expired.toString()); @@ -502,17 +511,12 @@ export const getPermits = async ( }; export const getPermitHistory = async ( - originalPermitId?: Nullable, - companyIdParam?: Nullable, + companyId: number, + originalPermitId: string, ) => { try { - if (!originalPermitId) return []; + if (!companyId || !originalPermitId) return []; - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); const response = await httpGETRequest( `${PERMITS_API_ROUTES.BASE(companyId)}/${originalPermitId}/history`, ); @@ -565,17 +569,13 @@ export const voidPermit = async (voidPermitParams: { /** * Amend a permit. * @param formData data for permit to be amended + * @param companyId id of the company that the permit belongs to * @returns Response with amended permit application, or error if failed */ export const amendPermit = async ( formData: AmendPermitFormData, - companyIdParam?: Nullable, + companyId: number, ): Promise> => { - const companyId = getDefaultRequiredVal( - "", - getCompanyIdFromSession(), - companyIdParam, - ); return await httpPOSTRequest( PERMITS_API_ROUTES.AMEND(companyId), replaceEmptyValuesWithNull({ @@ -589,7 +589,7 @@ export const amendPermit = async ( * Modify amendment application. * @param application amendment application data to be modified * @param applicationNumber application number of the amendment application - * @param companyIdParam company id + * @param companyId id of the company that the amendment application belongs to * @returns response with amended permit data, or error if failed */ export const modifyAmendmentApplication = async ({ @@ -599,7 +599,7 @@ export const modifyAmendmentApplication = async ({ }: { application: AmendPermitFormData; applicationId: string; - companyId: string; + companyId: number; }) => { return await updateApplication(application, applicationId, companyId); }; @@ -608,7 +608,7 @@ export const modifyAmendmentApplication = async ({ * Resend permit and/or receipt to email. * @param permitId Permit id of the permit to resend * @param email Email to resend to - * @param [fax] Fax number to resend to + * @param fax Fax number to resend to * @param notificationTypes Types of email notifications to send (EMAIL_PERMIT and/or EMAIL_RECEIPT) * @returns Response if the resend action was successful */ @@ -638,26 +638,3 @@ export const resendPermit = async ({ replaceEmptyValuesWithNull(data), ); }; - -export const updateApplicationQueueStatus = async ( - applicationId: string, - caseActivityType: CaseActivityType, - comment?: string, -) => { - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); - - const data: any = { - caseActivityType, - }; - - // Conditionally include the comment property if it is given as an argument and not an empty string - if (comment && comment.trim() !== "") { - data.comment = [comment]; - } - - const response = await httpPOSTRequest( - APPLICATION_QUEUE_API_ROUTES.UPDATE_QUEUE_STATUS(companyId, applicationId), - data, - ); - return response; -}; diff --git a/frontend/src/features/permits/components/dashboard/ApplicationStepPage.tsx b/frontend/src/features/permits/components/dashboard/ApplicationStepPage.tsx index 55f69dfcf..960f85e65 100644 --- a/frontend/src/features/permits/components/dashboard/ApplicationStepPage.tsx +++ b/frontend/src/features/permits/components/dashboard/ApplicationStepPage.tsx @@ -11,15 +11,20 @@ import { ApplicationReview } from "../../pages/Application/ApplicationReview"; import { useCompanyInfoQuery } from "../../../manageProfile/apiManager/hooks"; import { Loading } from "../../../../common/pages/Loading"; import { ErrorFallback } from "../../../../common/pages/ErrorFallback"; -import { useApplicationDetailsQuery } from "../../hooks/hooks"; +import { useApplicationForStepsQuery } from "../../hooks/hooks"; import { PERMIT_STATUSES } from "../../types/PermitStatus"; -import { getDefaultRequiredVal } from "../../../../common/helpers/util"; -import { DEFAULT_PERMIT_TYPE, PermitType, isPermitTypeValid } from "../../types/PermitType"; +import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { + DEFAULT_PERMIT_TYPE, + PermitType, + isPermitTypeValid, +} from "../../types/PermitType"; import { APPLICATION_STEPS, ApplicationStep, ERROR_ROUTES, } from "../../../../routes/constants"; +import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler"; const displayHeaderText = (stepKey: ApplicationStep) => { switch (stepKey) { @@ -39,6 +44,11 @@ export const ApplicationStepPage = ({ applicationStep: ApplicationStep; }) => { const companyInfoQuery = useCompanyInfoQuery(); + const companyId: number = getDefaultRequiredVal( + 0, + applyWhenNotNullable(id => Number(id), getCompanyIdFromSession()), + companyInfoQuery.data?.companyId, + ); // Get application number from route, if there is one (for edit applications) // or get the permit type for creating a new application @@ -50,7 +60,12 @@ export const ApplicationStepPage = ({ setApplicationData, shouldEnableQuery, isInvalidRoute, - } = useApplicationDetailsQuery(applicationStep, permitId, permitType); + } = useApplicationForStepsQuery({ + applicationStep, + permitId, + permitType, + companyId, + }); const contextData = useMemo( () => ({ @@ -68,7 +83,9 @@ export const ApplicationStepPage = ({ const applicationPermitType = getDefaultRequiredVal( DEFAULT_PERMIT_TYPE, - isPermitTypeValid(permitType) ? permitType?.toUpperCase() as PermitType : null, + isPermitTypeValid(permitType) + ? (permitType?.toUpperCase() as PermitType) + : null, applicationData?.permitType, ); @@ -86,7 +103,7 @@ export const ApplicationStepPage = ({ if (applicationStep === APPLICATION_STEPS.REVIEW) { return ; } - + return ; }; diff --git a/frontend/src/features/permits/components/dashboard/PermitLists.tsx b/frontend/src/features/permits/components/dashboard/PermitLists.tsx index a888cb4e5..1806f52fb 100644 --- a/frontend/src/features/permits/components/dashboard/PermitLists.tsx +++ b/frontend/src/features/permits/components/dashboard/PermitLists.tsx @@ -1,4 +1,5 @@ import React from "react"; + import { TabLayout } from "../../../../common/components/dashboard/TabLayout"; import { StartApplicationAction } from "../../pages/Application/components/dashboard/StartApplicationAction"; import { ActivePermitList } from "../permit-list/ActivePermitList"; @@ -7,10 +8,18 @@ import { ApplicationsInProgressList } from "../permit-list/ApplicationsInProgres import { ApplicationsInReviewList } from "../permit-list/ApplicationsInReviewList"; import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; import { RenderIf } from "../../../../common/components/reusable/RenderIf"; +import { applyWhenNotNullable } from "../../../../common/helpers/util"; +import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler"; export const PermitLists = React.memo(() => { const tabs = []; + const companyId: number = applyWhenNotNullable( + id => Number(id), + getCompanyIdFromSession(), + 0, + ); + const showApplicationsInProgressTab = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "MANAGE_PERMITS", @@ -21,7 +30,7 @@ export const PermitLists = React.memo(() => { if (showApplicationsInProgressTab) { tabs.push({ label: "Applications in Progress", - component: , + component: , }); } diff --git a/frontend/src/features/permits/components/dashboard/tests/integration/helpers/prepare.tsx b/frontend/src/features/permits/components/dashboard/tests/integration/helpers/prepare.tsx index 16d8e05f6..cfef07572 100644 --- a/frontend/src/features/permits/components/dashboard/tests/integration/helpers/prepare.tsx +++ b/frontend/src/features/permits/components/dashboard/tests/integration/helpers/prepare.tsx @@ -132,7 +132,7 @@ const server = setupServer( // Mock getting application http.get( - `${APPLICATIONS_API_ROUTES.GET(getDefaultUserDetails().companyId.toString())}/:permitId`, + `${APPLICATIONS_API_ROUTES.GET(getDefaultUserDetails().companyId.toString(), ":permitId")}`, () => { return HttpResponse.json({ // get application from mock application store (there's only 1 application or empty), since we're testing save/create/edit behaviour diff --git a/frontend/src/features/permits/components/form/ApplicationDetails.tsx b/frontend/src/features/permits/components/form/ApplicationDetails.tsx index d09669838..bb9842061 100644 --- a/frontend/src/features/permits/components/form/ApplicationDetails.tsx +++ b/frontend/src/features/permits/components/form/ApplicationDetails.tsx @@ -7,7 +7,10 @@ import "./ApplicationDetails.scss"; import { permitTypeDisplayText } from "../../types/PermitType"; import { CompanyProfile } from "../../../manageProfile/types/manageProfile"; import { Nullable } from "../../../../common/types/common"; -import { DATE_FORMATS, dayjsToLocalStr } from "../../../../common/helpers/formatDate"; +import { + DATE_FORMATS, + dayjsToLocalStr, +} from "../../../../common/helpers/formatDate"; import { applyWhenNotNullable, getDefaultRequiredVal, diff --git a/frontend/src/features/permits/components/list/List.tsx b/frontend/src/features/permits/components/list/List.tsx deleted file mode 100644 index 86377e506..000000000 --- a/frontend/src/features/permits/components/list/List.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { RowSelectionState } from "@tanstack/table-core"; -import { Box, IconButton, Tooltip } from "@mui/material"; -import { UseQueryResult } from "@tanstack/react-query"; -import { Delete } from "@mui/icons-material"; -import { - memo, - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from "react"; - -import { - MRT_ColumnDef, - MRT_GlobalFilterTextField, - MRT_Row, - MRT_TableInstance, - MaterialReactTable, - useMaterialReactTable, -} from "material-react-table"; - -import "./List.scss"; -import { TrashButton } from "../../../../common/components/buttons/TrashButton"; -import { DeleteConfirmationDialog } from "../../../../common/components/dialog/DeleteConfirmationDialog"; -import { SnackBarContext } from "../../../../App"; -import { ApplicationListItem } from "../../types/application"; -import { ApplicationInProgressColumnDefinition } from "./Columns"; -import { deleteApplications } from "../../apiManager/permitsAPI"; -import { NoRecordsFound } from "../../../../common/components/table/NoRecordsFound"; -import { - defaultTableOptions, - defaultTableStateOptions, - defaultTableInitialStateOptions, -} from "../../../../common/helpers/tableHelper"; - -/** - * Dynamically set the column - * @returns An array of column headers/accessor keys for Material React Table - */ -const getColumns = (): MRT_ColumnDef[] => { - return ApplicationInProgressColumnDefinition; -}; - -/* - * - * The List component uses Material React Table (MRT) - * For detailed documentation, see here: - * https://www.material-react-table.com/docs/getting-started/usage - * - * - */ -/* eslint-disable react/prop-types */ -export const List = memo( - ({ query }: { query: UseQueryResult }) => { - // Data, fetched from backend API - const { data, isError, isFetching, isPending } = query; - - const columns = useMemo[]>( - () => getColumns(), - [], - ); - - const snackBar = useContext(SnackBarContext); - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - const [rowSelection, setRowSelection] = useState({}); - const hasNoRowsSelected = Object.keys(rowSelection).length === 0; - - /** - * Callback function for clicking on the Trash icon above the Table. - */ - const onClickTrashIcon = useCallback(() => { - setIsDeleteDialogOpen(() => true); - }, []); - - /** - * Function that deletes a application once the user confirms the delete action - * in the confirmation dialog. - */ - const onConfirmApplicationDelete = async () => { - const applicationIds: string[] = Object.keys(rowSelection); - const response = await deleteApplications(applicationIds); - if (response.status === 201 || response.status === 200) { - const responseBody = response.data; - setIsDeleteDialogOpen(() => false); - if (responseBody.failure.length > 0) { - snackBar.setSnackBar({ - alertType: "error", - message: "An unexpected error occurred.", - setShowSnackbar: () => true, - showSnackbar: true, - }); - } else { - snackBar.setSnackBar({ - message: "Application Deleted", - alertType: "info", - setShowSnackbar: () => true, - showSnackbar: true, - }); - } - setRowSelection(() => { - return {}; - }); - query.refetch(); - } - }; - - useEffect(() => { - if (isError) { - snackBar.setSnackBar({ - message: "An unexpected error occurred.", - showSnackbar: true, - setShowSnackbar: () => true, - alertType: "error", - }); - } - }, [isError]); - - /** - * Function that clears the delete related states when the user clicks on cancel. - */ - const onCancelApplicationDelete = useCallback(() => { - setRowSelection(() => { - return {}; - }); - setIsDeleteDialogOpen(() => false); - }, []); - - const table = useMaterialReactTable({ - ...defaultTableOptions, - columns: columns, - data: data ?? [], - state: { - ...defaultTableStateOptions, - showAlertBanner: isError, - showProgressBars: isFetching, - columnVisibility: { applicationId: true }, - rowSelection: rowSelection, - isLoading: isPending, - }, - initialState: { - ...defaultTableInitialStateOptions, - }, - onRowSelectionChange: setRowSelection, - getRowId: (originalRow) => { - const applicationRow = originalRow as ApplicationListItem; - return applicationRow.permitId; - }, - renderEmptyRowsFallback: () => , - renderRowActions: useCallback( - ({ - row, - }: { - table: MRT_TableInstance; - row: MRT_Row; - }) => ( - - - { - setIsDeleteDialogOpen(() => true); - setRowSelection(() => { - const newObject: { [key: string]: boolean } = {}; - // Setting the selected row to false so that - // the row appears unchecked. - newObject[row.original.permitId] = false; - return newObject; - }); - }} - disabled={false} - > - - - - - ), - [], - ), - // Render a custom options Bar (inclues search and trash) - renderTopToolbar: useCallback( - ({ table }: { table: MRT_TableInstance }) => ( - - - - - ), - [hasNoRowsSelected], - ), - - muiToolbarAlertBannerProps: isError - ? { - color: "error", - children: "Error loading data", - } - : undefined, - }); - - return ( -
- - -
- ); - }, -); - -List.displayName = "List"; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx index 38c57682e..edabb1b07 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx @@ -4,7 +4,7 @@ import { OnRouteBCChip } from "../../../../common/components/chip/OnRouteBCChip" import { APPLICATION_QUEUE_STATUSES, ApplicationQueueStatus, -} from "../../types/ApplicationQueueStatus"; +} from "../../../queue/types/ApplicationQueueStatus"; /** * Returns the theme name for the chip based on the permit status. diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx index 9f5c83c12..2415ea17e 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInProgressList.tsx @@ -42,20 +42,24 @@ const getColumns = ( return ApplicationInProgressColumnDefinition(userRole); }; -export const ApplicationsInProgressList = () => { +export const ApplicationsInProgressList = ({ + companyId, +}: { + companyId: number; +}) => { const { applicationsInProgressQuery, pagination, setPagination, sorting, setSorting, - } = useApplicationsInProgressQuery(); + } = useApplicationsInProgressQuery(companyId); const { pendingPermits, pagination: pendingPermitPagination, setPagination: setPendingPermitPagination, - } = usePendingPermitsQuery(); + } = usePendingPermitsQuery(companyId); const { data: applicationsInProgress, @@ -104,7 +108,7 @@ export const ApplicationsInProgressList = () => { const onConfirmApplicationDelete = async () => { const applicationIds: string[] = Object.keys(rowSelection); - const response = await deleteApplications(applicationIds); + const response = await deleteApplications(companyId, applicationIds); if (response.status === 200) { const responseBody = response.data; setIsDeleteDialogOpen(() => false); diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx index f2896a377..310b12726 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx @@ -15,7 +15,6 @@ import { getDefaultNullableVal, getDefaultRequiredVal, } from "../../../../common/helpers/util"; -import { useApplicationsInQueueQuery } from "../../hooks/hooks"; import { InfoBcGovBanner } from "../../../../common/components/banners/InfoBcGovBanner"; import { defaultTableInitialStateOptions, @@ -25,7 +24,8 @@ import { import { BANNER_MESSAGES } from "../../../../common/constants/bannerMessages"; import { MRT_Row } from "material-react-table"; import { ApplicationsInReviewRowOptions } from "./ApplicationsInReviewRowOptions"; -import { APPLICATION_QUEUE_STATUSES } from "../../types/ApplicationQueueStatus"; +import { APPLICATION_QUEUE_STATUSES } from "../../../queue/types/ApplicationQueueStatus"; +import { useApplicationsInQueueQuery } from "../../../queue/hooks/hooks"; export const ApplicationsInReviewList = () => { const { @@ -43,14 +43,14 @@ export const ApplicationsInReviewList = () => { isFetching, } = applicationsInQueueQuery; - const [showAIRTable, setShowAIRTable] = useState(false); + const [showTable, setShowTable] = useState(false); useEffect(() => { const totalCount = getDefaultRequiredVal( 0, applicationsInQueue?.meta?.totalItems, ); - setShowAIRTable(totalCount > 0); + setShowTable(totalCount > 0); }, [applicationsInQueue?.meta?.totalItems]); const { idirUserDetails, userDetails } = useContext(OnRouteBCContext); @@ -149,7 +149,7 @@ export const ApplicationsInReviewList = () => { return ( <> - {showAIRTable ? ( + {showTable ? (
{ - if (isError) { + if (isWithdrawApplicationError) { // if the application has already been withdrawn by another user - if (error.response?.status === 422) { + if (withdrawApplicationError.response?.status === 422) { return setIsAIRModalOpen(true); } // handle all other errors navigate(ERROR_ROUTES.UNEXPECTED); } - }, [isError, error]); + }, [isWithdrawApplicationError, withdrawApplicationError]); /** * Action handler upon a select event. * @param selectedOption The option that was selected. */ - const onSelectOptionCallback = (selectedOption: string) => { + const onSelectOptionCallback = async (selectedOption: string) => { if (selectedOption === PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION) { - mutateAsync({ - applicationId: permitId, - caseActivityType: CASE_ACTIVITY_TYPES.WITHDRAWN, - }); + await withdrawApplication(permitId); } }; diff --git a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx index 340b8bd33..e0bbda52a 100644 --- a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx +++ b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx @@ -30,6 +30,7 @@ import { isPermitInactive } from "../../types/PermitStatus"; import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; import { DoesUserHaveRole } from "../../../../common/authentication/util"; import { IDIR_USER_ROLE } from "../../../../common/authentication/types"; +import { applyWhenNotNullable } from "../../../../common/helpers/util"; /** * A permit list component with common functionalities that can be shared by @@ -40,7 +41,17 @@ export const BasePermitList = ({ }: { isExpired?: boolean; }) => { - const { idirUserDetails } = useContext(OnRouteBCContext); + const { + idirUserDetails, + companyId: companyIdFromContext, + } = useContext(OnRouteBCContext); + + const companyId: number = applyWhenNotNullable( + id => Number(id), + companyIdFromContext, + 0, + ); + const navigate = useNavigate(); const [pagination, setPagination] = useState({ pageIndex: 0, @@ -65,6 +76,7 @@ export const BasePermitList = ({ ], queryFn: () => getPermits( + companyId, { expired: isExpired }, { page: pagination.pageIndex, @@ -84,6 +96,7 @@ export const BasePermitList = ({ placeholderData: keepPreviousData, refetchOnWindowFocus: false, retry: 1, + enabled: Boolean(companyId), }); const { data, isError, isPending, isRefetching } = permitsQuery; @@ -152,11 +165,12 @@ export const BasePermitList = ({ permitNumber={row.original.permitNumber} permitId={row.original.permitId} userRole={idirUserDetails?.userRole} - companyId={row.original.companyId?.toString()} + companyId={row.original.companyId} /> ) : ( {navigate(ERROR_ROUTES.DOCUMENT_UNAVAILABLE)}} /> diff --git a/frontend/src/features/permits/components/permit-list/Columns.tsx b/frontend/src/features/permits/components/permit-list/Columns.tsx index cc9cd720a..c27bd3ee6 100644 --- a/frontend/src/features/permits/components/permit-list/Columns.tsx +++ b/frontend/src/features/permits/components/permit-list/Columns.tsx @@ -1,4 +1,5 @@ import { MRT_ColumnDef } from "material-react-table"; +import { Box, Tooltip } from "@mui/material"; import { viewPermitPdf } from "../../helpers/permitPDFHelper"; import { PermitListItem } from "../../types/permit"; @@ -7,12 +8,13 @@ import { formatCellValuetoDatetime } from "../../../../common/helpers/tableHelpe import { CustomActionLink } from "../../../../common/components/links/CustomActionLink"; import { getDefaultRequiredVal } from "../../../../common/helpers/util"; import { getPermitTypeName } from "../../types/PermitType"; -import { Box, Tooltip } from "@mui/material"; /** * The column definition for Permits. */ -export const PermitsColumnDefinition = (onDocumentUnavailable: () => void): MRT_ColumnDef[] => [ +export const PermitsColumnDefinition = ( + onDocumentUnavailable: () => void, +): MRT_ColumnDef[] => [ { accessorKey: "permitNumber", id: "permitNumber", @@ -25,8 +27,10 @@ export const PermitsColumnDefinition = (onDocumentUnavailable: () => void): MRT_ <> viewPermitPdf( + props.row.original.companyId, props.row.original.permitId, - () => onDocumentUnavailable())} + () => onDocumentUnavailable(), + )} > {props.cell.getValue()} diff --git a/frontend/src/features/permits/components/permit-list/PermitChip.tsx b/frontend/src/features/permits/components/permit-list/PermitChip.tsx index d418882ac..294948be6 100644 --- a/frontend/src/features/permits/components/permit-list/PermitChip.tsx +++ b/frontend/src/features/permits/components/permit-list/PermitChip.tsx @@ -5,7 +5,7 @@ import { PERMIT_STATUSES, isPermitInactive, } from "../../types/PermitStatus"; -import { APPLICATION_QUEUE_STATUSES } from "../../types/ApplicationQueueStatus"; +import { APPLICATION_QUEUE_STATUSES } from "../../../queue/types/ApplicationQueueStatus"; /** * Returns the theme name for the chip based on the permit status. diff --git a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx index ece4437dc..621af7bdd 100644 --- a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx +++ b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx @@ -32,10 +32,12 @@ const getOptions = (isExpired: boolean) => { export const PermitRowOptions = ({ isExpired, + companyId, permitId, onDocumentUnavailable, }: { isExpired: boolean; + companyId: number; permitId: string; onDocumentUnavailable: () => void; }) => { @@ -45,7 +47,7 @@ export const PermitRowOptions = ({ */ const onSelectOptionCallback = (selectedOption: string) => { if (selectedOption === PERMIT_ACTION_OPTION_TYPES.VIEW_RECEIPT) { - viewReceiptPdf(permitId, () => onDocumentUnavailable()); + viewReceiptPdf(companyId, permitId, () => onDocumentUnavailable()); } }; diff --git a/frontend/src/features/permits/context/CartContextProvider.tsx b/frontend/src/features/permits/context/CartContextProvider.tsx index 62fb52d07..4947c4192 100644 --- a/frontend/src/features/permits/context/CartContextProvider.tsx +++ b/frontend/src/features/permits/context/CartContextProvider.tsx @@ -3,7 +3,7 @@ import React, { useContext, useMemo } from "react"; import { CartContext } from "./CartContext"; import OnRouteBCContext from "../../../common/authentication/OnRouteBCContext"; import { useGetCartCount } from "../hooks/cart"; -import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../common/helpers/util"; +import { getDefaultRequiredVal } from "../../../common/helpers/util"; export const CartContextProvider = ({ children, @@ -14,7 +14,7 @@ export const CartContextProvider = ({ // Set cart count for company const cartCountQuery = useGetCartCount( - applyWhenNotNullable(id => `${id}`, companyId), + getDefaultRequiredVal(0, companyId), ); const { data: fetchedCartCount } = cartCountQuery; diff --git a/frontend/src/features/permits/helpers/permitPDFHelper.ts b/frontend/src/features/permits/helpers/permitPDFHelper.ts index c20dc06e8..ad92df8f7 100644 --- a/frontend/src/features/permits/helpers/permitPDFHelper.ts +++ b/frontend/src/features/permits/helpers/permitPDFHelper.ts @@ -16,50 +16,49 @@ export const openBlobInNewTab = (blob: Blob) => { }; /** - * Opens the receipt pdf in a new tab. - * @param permitId The permit id. + * Opens the receipt pdf for a permit in a new tab. + * @param companyId id of the company that the permit belongs to + * @param permitId The permit id of the receipt + * @param onDocumentUnavailable Callback function invoked when the PDF document is unavailable */ export const viewReceiptPdf = async ( + companyId: number, permitId: string, onDocumentUnavailable?: () => void, - companyId?: string, ) => { if (permitId) { try { const { blobObj: blobObjWithoutType } = await downloadReceiptPdf( - permitId, companyId, + permitId, ); openBlobInNewTab(blobObjWithoutType); } catch (err) { console.error(err); - if (onDocumentUnavailable) { - onDocumentUnavailable(); - } + onDocumentUnavailable?.(); } } }; /** * Opens the permit PDF in a new tab. - * @param permitId The permitId of the permit. + * @param companyId id of the company that the permit belongs to + * @param permitId The id of the permit + * @param onDocumentUnavailable Callback function invoked when the PDF document is unavailable */ export const viewPermitPdf = async ( + companyId: number, permitId: string, onDocumentUnavailable?: () => void, - companyId?: string, - ) => { try { const { blobObj: blobObjWithoutType } = await downloadPermitApplicationPdf( - permitId, companyId, + permitId, ); openBlobInNewTab(blobObjWithoutType); } catch (err) { console.error(err); - if (onDocumentUnavailable) { - onDocumentUnavailable(); - } + onDocumentUnavailable?.(); } }; diff --git a/frontend/src/features/permits/hooks/cart.ts b/frontend/src/features/permits/hooks/cart.ts index 747a86e26..5299c7724 100644 --- a/frontend/src/features/permits/hooks/cart.ts +++ b/frontend/src/features/permits/hooks/cart.ts @@ -1,10 +1,14 @@ import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { addToCart, fetchCart, getCartCount, removeFromCart } from "../apiManager/cart"; import { Nullable } from "../../../common/types/common"; -import { getDefaultRequiredVal } from "../../../common/helpers/util"; -import { getApplicationByPermitId } from "../apiManager/permitsAPI"; +import { getApplication } from "../apiManager/permitsAPI"; +import { + addToCart, + fetchCart, + getCartCount, + removeFromCart, +} from "../apiManager/cart"; const CART_KEY = "cart"; const CART_COUNT_KEY = "cart-count"; @@ -20,7 +24,7 @@ export const useAddToCart = () => { companyId, applicationIds, }: { - companyId: string; + companyId: number; applicationIds: string[]; }) => addToCart(companyId, applicationIds), }); @@ -33,7 +37,7 @@ export const useAddToCart = () => { * @returns Query object for fetching cart items */ export const useFetchCart = ( - companyId: string, + companyId: number, fetchAllApplications?: boolean, ) => { return useQuery({ @@ -56,7 +60,7 @@ export const useRemoveFromCart = () => { companyId, applicationIds, }: { - companyId: string; + companyId: number; applicationIds: string[]; }) => removeFromCart(companyId, applicationIds), }); @@ -67,12 +71,10 @@ export const useRemoveFromCart = () => { * @param companyId id of company to get cart item count for * @returns Query object used for getting cart item count */ -export const useGetCartCount = (companyId?: Nullable) => { - const cartCompanyId = getDefaultRequiredVal("", companyId); - +export const useGetCartCount = (companyId: number) => { return useQuery({ queryKey: [CART_COUNT_KEY, companyId], - queryFn: () => getCartCount(cartCompanyId), + queryFn: () => getCartCount(companyId), enabled: Boolean(companyId), retry: false, refetchOnMount: "always", @@ -81,17 +83,18 @@ export const useGetCartCount = (companyId?: Nullable) => { }; /** - * Hook used to fetch the latest status of a cart item./ + * Hook used to fetch the latest status of a cart item. + * @param companyId id of the company that the cart belongs to * @returns Latest status of selected cart item and method to fetch its latest status */ -export const useFetchCartItemStatus = () => { +export const useFetchCartItemStatus = (companyId: number) => { const queryClient = useQueryClient(); const [cartItemId, setCartItemId] = useState>(); const cartItemDetailQuery = useQuery({ queryKey: [CART_ITEM, cartItemId], - queryFn: () => getApplicationByPermitId(cartItemId), - enabled: Boolean(cartItemId), + queryFn: () => getApplication(companyId, cartItemId as string), + enabled: Boolean(companyId) && Boolean(cartItemId), retry: false, refetchOnMount: "always", refetchOnWindowFocus: false, diff --git a/frontend/src/features/permits/hooks/hooks.ts b/frontend/src/features/permits/hooks/hooks.ts index bfedb1884..d3a39aeb2 100644 --- a/frontend/src/features/permits/hooks/hooks.ts +++ b/frontend/src/features/permits/hooks/hooks.ts @@ -1,6 +1,6 @@ -import { useState, useEffect, useContext } from "react"; +import { useState, useEffect } from "react"; import { AxiosError } from "axios"; -import { MRT_PaginationState, MRT_SortingState } from "material-react-table"; +import { MRT_PaginationState } from "material-react-table"; import { useQueryClient, useMutation, @@ -18,8 +18,9 @@ import { deserializeApplicationResponse } from "../helpers/deserializeApplicatio import { deserializePermitResponse } from "../helpers/deserializePermit"; import { AmendPermitFormData } from "../pages/Amend/types/AmendPermitFormData"; import { Nullable, Optional } from "../../../common/types/common"; +import { useTableControls } from "./useTableControls"; import { - getApplicationByPermitId, + getApplication, getPermit, getPermitHistory, completeTransaction, @@ -33,27 +34,21 @@ import { getApplicationsInProgress, resendPermit, getPendingPermits, - getApplicationsInQueue, - updateApplicationQueueStatus, } from "../apiManager/permitsAPI"; -import { - CASE_ACTIVITY_TYPES, - CaseActivityType, -} from "../types/CaseActivityType"; -import { SnackBarContext } from "../../../App"; +import { getDefaultRequiredVal } from "../../../common/helpers/util"; const QUERY_KEYS = { PERMIT_DETAIL: ( permitId?: Nullable, - companyId?: Nullable, + companyId?: Nullable, ) => ["permit", permitId, companyId], AMEND_APPLICATION: ( originalPermitId?: Nullable, - companyId?: Nullable, + companyId?: Nullable, ) => ["amendmentApplication", originalPermitId, companyId], PERMIT_HISTORY: ( originalPermitId?: Nullable, - companyId?: Nullable, + companyId?: Nullable, ) => ["permitHistory", originalPermitId, companyId], }; @@ -64,10 +59,16 @@ const QUERY_KEYS = { export const useSaveApplicationMutation = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async (data: ApplicationFormData) => { + mutationFn: async ({ + data, + companyId, + }: { + data: ApplicationFormData; + companyId: number; + }) => { const res = data.permitId - ? await updateApplication(data, data.permitId) - : await createApplication(data); + ? await updateApplication(data, data.permitId, companyId) + : await createApplication(data, companyId); if (res.status === 200 || res.status === 201) { queryClient.invalidateQueries({ @@ -89,18 +90,24 @@ export const useSaveApplicationMutation = () => { }; /** - * A custom react query hook that get application details from the backend API - * The hook gets application data by its permit ID - * @param applicationStep The step of the application process (form, review, or pay) - * @param permitId permit id for the application, if it exists - * @param permitType permit type for the application, if it exists - * @returns appropriate Application data, or error if failed + * Hook that gets application details for the application steps. + * @param applicationStep The step of the application process (ie. form or review) + * @param permitId Id of the application to get details for + * @param permitType Permit type of the application + * @param companyId Company id that the application belongs to + * @returns Application data and supplementary info with route validation for application steps */ -export const useApplicationDetailsQuery = ( - applicationStep: ApplicationStep, - permitId?: string, - permitType?: string, -) => { +export const useApplicationForStepsQuery = ({ + applicationStep, + permitId, + permitType, + companyId, +}: { + applicationStep: ApplicationStep; + permitId?: Nullable; + permitType?: Nullable; + companyId: number; +}) => { const [applicationData, setApplicationData] = useState>(); @@ -121,17 +128,10 @@ export const useApplicationDetailsQuery = ( // We also need applicationStep to determine which page (route) we're on, and check the permit type route param const isInvalidRoute = !isPermitIdNumeric(permitId) && !isCreateNewApplication; - const shouldEnableQuery = isPermitIdNumeric(permitId); - // This won't fetch anything (ie. query.data will be undefined) if shouldEnableQuery is false - const query = useQuery({ - queryKey: ["application"], - queryFn: () => getApplicationByPermitId(permitId), - retry: false, - refetchOnMount: "always", // always fetch when component is mounted (ApplicationDashboard page) - refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground - enabled: shouldEnableQuery, // does not perform the query at all if permit id is invalid - gcTime: 0, // DO NOT cache the application data as application form/review pages always need latest data + const { query, shouldEnableQuery } = useApplicationDetailsQuery({ + permitId: getDefaultRequiredVal("", permitId), + companyId, }); useEffect(() => { @@ -159,22 +159,54 @@ export const useApplicationDetailsQuery = ( }; /** - * A custom react query hook that get permit details from the backend API - * The hook gets permit details by its permit id + * Query hook that gets application details from the backend API + * @param permitId Id for the application, if it exists + * @param companyId Company id that the application belongs to + * @returns Query object containing application details, and flag indicating whether query is enabled + */ +export const useApplicationDetailsQuery = ({ + permitId, + companyId, +}: { + permitId: string; + companyId: number; +}) => { + const shouldEnableQuery = isPermitIdNumeric(permitId) && Boolean(companyId); + + // This won't fetch anything (ie. query.data will be undefined) if shouldEnableQuery is false + const query = useQuery({ + queryKey: ["application"], + queryFn: () => getApplication(companyId, permitId), + retry: false, + refetchOnMount: "always", // always fetch when component is mounted + refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground + enabled: shouldEnableQuery, // does not perform the query at all if shouldEnableQuery is false + gcTime: 0, // DO NOT cache the application data as application form/review pages always need latest data + }); + + return { + query, + shouldEnableQuery, + }; +}; + +/** + * A custom react query hook that get permit details from the backend API. + * @param companyId id of the company that the permit belongs to * @param permitId permit id for the permit - * @returns UseQueryResult for permit query. + * @returns Query object containing the permit details */ export const usePermitDetailsQuery = ( - companyId: Nullable, - permitId?: Nullable, + companyId: number, + permitId: string, ) => { return useQuery({ queryKey: QUERY_KEYS.PERMIT_DETAIL(permitId, companyId), queryFn: async () => { - const res = await getPermit(permitId, companyId); + const res = await getPermit(companyId, permitId); return res ? deserializePermitResponse(res) : res; }, - enabled: Boolean(permitId) && Boolean(companyId), + enabled: isPermitIdNumeric(permitId) && Boolean(companyId), retry: false, refetchOnMount: "always", refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground @@ -275,19 +307,19 @@ export const useCompleteTransaction = ( }; /** - * A custom react query hook that get permit history from the backend API - * The hook gets permit history by its original permit id + * A custom react query hook that get permit history from the backend API. + * @param companyId id of the company that the original permit belongs to * @param originalPermitId original permit id for the permit - * @returns UseQueryResult of the fetch query. + * @returns Query object containing permit history information */ export const usePermitHistoryQuery = ( - originalPermitId?: Nullable, - companyId?: Nullable, + companyId: number, + originalPermitId: string, ) => { return useQuery({ queryKey: QUERY_KEYS.PERMIT_HISTORY(originalPermitId, companyId), - queryFn: () => getPermitHistory(originalPermitId, companyId), - enabled: Boolean(originalPermitId) && Boolean(companyId), + queryFn: () => getPermitHistory(companyId, originalPermitId), + enabled: Boolean(companyId) && isPermitIdNumeric(originalPermitId), retry: false, refetchOnMount: "always", refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground @@ -296,18 +328,20 @@ export const usePermitHistoryQuery = ( /** * Custom hook that issues the permits indicated by the application/permit ids. - * @param ids Application/permit ids for the permits to be issued. * @returns Mutation object, and the issued results response. */ -export const useIssuePermits = (companyIdParam?: Nullable) => { +export const useIssuePermits = () => { const [issueResults, setIssueResults] = useState>(undefined); const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: (applicationIds: string[]) => - issuePermits(applicationIds, companyIdParam), + mutationFn: (data: { + companyId: number; + applicationIds: string[]; + }) => + issuePermits(data.companyId, data.applicationIds), retry: false, onSuccess: (issueResponseData) => { queryClient.invalidateQueries({ @@ -328,27 +362,29 @@ export const useIssuePermits = (companyIdParam?: Nullable) => { }; /** - * A custom react query mutation hook that requests the backend API to amend the permit. + * A custom mutation hook that requests the backend API to amend the permit. + * @param companyId id of the company that the amended permit belongs to + * @returns Mutation object that contains the result of the amendment action */ -export const useAmendPermit = (companyIdParam?: Nullable) => { +export const useAmendPermit = (companyId: number) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (data: AmendPermitFormData) => { - const amendResult = await amendPermit(data, companyIdParam); + const amendResult = await amendPermit(data, companyId); if (amendResult.status === 200 || amendResult.status === 201) { queryClient.invalidateQueries({ - queryKey: QUERY_KEYS.PERMIT_DETAIL(data.permitId, companyIdParam), + queryKey: QUERY_KEYS.PERMIT_DETAIL(data.permitId, companyId), }); queryClient.invalidateQueries({ queryKey: QUERY_KEYS.AMEND_APPLICATION( data.originalPermitId, - companyIdParam, + companyId, ), }); queryClient.invalidateQueries({ queryKey: QUERY_KEYS.PERMIT_HISTORY( data.originalPermitId, - companyIdParam, + companyId, ), }); @@ -371,7 +407,7 @@ export const useModifyAmendmentApplication = () => { mutationFn: async (data: { application: AmendPermitFormData; applicationId: string; - companyId: string; + companyId: number; }) => { const amendResult = await modifyAmendmentApplication(data); @@ -404,23 +440,24 @@ export const useModifyAmendmentApplication = () => { /** * A custom react query hook that gets the current amendment application, if there is one. - * @param originalPermitId Original permit id of the permit that is being amended. - * @returns UseQueryResult of the fetch query. + * @param companyId id of the company that the original permit belongs to + * @param originalPermitId Original permit id of the permit that is being amended + * @returns Query object containing information regarding the current amendment application if it exists */ export const useAmendmentApplicationQuery = ( - originalPermitId?: Nullable, - companyId?: Nullable, + companyId: number, + originalPermitId: string, ) => { return useQuery({ queryKey: QUERY_KEYS.AMEND_APPLICATION(originalPermitId, companyId), queryFn: async () => { const res = await getCurrentAmendmentApplication( - originalPermitId, companyId, + originalPermitId, ); return res ? deserializeApplicationResponse(res) : res; }, - enabled: Boolean(originalPermitId), + enabled: Boolean(companyId) && isPermitIdNumeric(originalPermitId), retry: false, refetchOnMount: "always", refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground @@ -429,30 +466,12 @@ export const useAmendmentApplicationQuery = ( /** * A custom react query hook that fetches applications in progress and manages its pagination state. + * @param companyId id of the company to fetch applications for * @returns Query object containing fetched applications in progress, along with pagination state and setters */ -export const useApplicationsInProgressQuery = () => { - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 10, - }); - - const [sorting, setSorting] = useState([ - { - id: "updatedDateTime", - desc: true, - }, - ]); - - const orderBy = - sorting.length > 0 - ? [ - { - column: sorting.at(0)?.id as string, - descending: Boolean(sorting.at(0)?.desc), - }, - ] - : []; +export const useApplicationsInProgressQuery = (companyId: number) => { + const { pagination, setPagination, sorting, setSorting, orderBy } = + useTableControls(); const applicationsInProgressQuery = useQuery({ queryKey: [ @@ -462,14 +481,18 @@ export const useApplicationsInProgressQuery = () => { sorting, ], queryFn: () => - getApplicationsInProgress({ - page: pagination.pageIndex, - take: pagination.pageSize, - orderBy, - }), + getApplicationsInProgress( + companyId, + { + page: pagination.pageIndex, + take: pagination.pageSize, + orderBy, + }, + ), refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground refetchOnMount: "always", placeholderData: keepPreviousData, + enabled: Boolean(companyId), }); return { @@ -483,9 +506,10 @@ export const useApplicationsInProgressQuery = () => { /** * Hook that fetches pending permits and manages its pagination state. + * @param companyId id of the company to fetch applications for * @returns Pending permits along with pagination state and setter */ -export const usePendingPermitsQuery = () => { +export const usePendingPermitsQuery = (companyId: number) => { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, @@ -494,13 +518,17 @@ export const usePendingPermitsQuery = () => { const { data: pendingPermits } = useQuery({ queryKey: ["pendingPermits", pagination.pageIndex, pagination.pageSize], queryFn: () => - getPendingPermits({ - page: pagination.pageIndex, - take: pagination.pageSize, - }), + getPendingPermits( + companyId, + { + page: pagination.pageIndex, + take: pagination.pageSize, + }, + ), refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground refetchOnMount: "always", placeholderData: keepPreviousData, + enabled: Boolean(companyId), }); return { @@ -510,104 +538,6 @@ export const usePendingPermitsQuery = () => { }; }; -/** - * Hook that fetches applications in queue (PENDING_REVIEW, IN_REVIEW) and manages its pagination state. - * @returns Applications in queue along with pagination state and setter - */ -export const useApplicationsInQueueQuery = () => { - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 10, - }); - - const [sorting, setSorting] = useState([ - { - id: "updatedDateTime", - desc: true, - }, - ]); - - const orderBy = - sorting.length > 0 - ? [ - { - column: sorting.at(0)?.id as string, - descending: Boolean(sorting.at(0)?.desc), - }, - ] - : []; - - const applicationsInQueueQuery = useQuery({ - queryKey: [ - "applicationsInQueue", - pagination.pageIndex, - pagination.pageSize, - sorting, - ], - queryFn: () => - getApplicationsInQueue({ - page: pagination.pageIndex, - take: pagination.pageSize, - orderBy, - }), - refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground - refetchOnMount: "always", - placeholderData: keepPreviousData, - }); - - return { - applicationsInQueueQuery, - pagination, - setPagination, - sorting, - setSorting, - }; -}; - -export const useUpdateApplicationQueueStatusMutation = () => { - const { invalidate } = useInvalidateApplicationsInQueue(); - const { setSnackBar } = useContext(SnackBarContext); - - return useMutation({ - mutationFn: (data: { - applicationId: string; - caseActivityType: CaseActivityType; - comment?: string; - }) => { - const { applicationId, caseActivityType, comment } = data; - - return updateApplicationQueueStatus( - applicationId, - caseActivityType, - comment, - ); - }, - onSuccess: (_data, variables) => { - const { caseActivityType } = variables; - if (caseActivityType === CASE_ACTIVITY_TYPES.WITHDRAWN) { - setSnackBar({ - showSnackbar: true, - setShowSnackbar: () => true, - message: "Withdrawn to Applications in Progress", - alertType: "info", - }); - invalidate(); - } - }, - onError: (err: AxiosError) => err, - }); -}; - -export const useInvalidateApplicationsInQueue = () => { - const queryClient = useQueryClient(); - - return { - invalidate: () => { - queryClient.invalidateQueries({ queryKey: ["applicationsInQueue"] }); - }, - }; -}; - /** * Hook used for resending a permit. * @returns Mutation object to be used for resending a permit diff --git a/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts b/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts index 8c062b943..61cd9f57a 100644 --- a/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts +++ b/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts @@ -25,7 +25,7 @@ import { VehicleType, } from "../../manageVehicles/types/Vehicle"; -export const usePermitVehicleManagement = (companyId: string) => { +export const usePermitVehicleManagement = (companyId: number) => { // Mutations used to add/update vehicle details const addPowerUnitMutation = useAddPowerUnitMutation(); const updatePowerUnitMutation = useUpdatePowerUnitMutation(); diff --git a/frontend/src/features/permits/hooks/useTableControls.ts b/frontend/src/features/permits/hooks/useTableControls.ts new file mode 100644 index 000000000..0ba61ec77 --- /dev/null +++ b/frontend/src/features/permits/hooks/useTableControls.ts @@ -0,0 +1,34 @@ +import { useState } from "react"; +import { MRT_PaginationState, MRT_SortingState } from "material-react-table"; + +export const useTableControls = ({ pageSize = 10 } = {}) => { + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize, + }); + + const [sorting, setSorting] = useState([ + { + id: "updatedDateTime", + desc: true, + }, + ]); + + const orderBy = + sorting.length > 0 + ? [ + { + column: sorting[0]?.id as string, + descending: Boolean(sorting[0]?.desc), + }, + ] + : []; + + return { + pagination, + setPagination, + sorting, + setSorting, + orderBy, + }; +}; diff --git a/frontend/src/features/permits/pages/Amend/AmendPermit.tsx b/frontend/src/features/permits/pages/Amend/AmendPermit.tsx index 035799f74..009109399 100644 --- a/frontend/src/features/permits/pages/Amend/AmendPermit.tsx +++ b/frontend/src/features/permits/pages/Amend/AmendPermit.tsx @@ -15,7 +15,7 @@ import { AmendPermitFinish } from "./components/AmendPermitFinish"; import { AmendPermitForm } from "./components/AmendPermitForm"; import { ERROR_ROUTES, IDIR_ROUTES } from "../../../../routes/constants"; import { hasPermitExpired } from "../../helpers/permitState"; -import { getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../common/helpers/util"; import { Application } from "../../types/application"; import { Nullable } from "../../../../common/types/common"; import { @@ -64,8 +64,7 @@ const isAmendable = (permit: Permit) => { const isAmendableByUser = (role?: string) => { return ( - role === USER_ROLE.PPC_CLERK || - role === USER_ROLE.SYSTEM_ADMINISTRATOR + role === USER_ROLE.PPC_CLERK || role === USER_ROLE.SYSTEM_ADMINISTRATOR ); }; @@ -74,7 +73,14 @@ const searchRoute = `&searchByFilter=${SEARCH_BY_FILTERS.PERMIT_NUMBER}&searchString=`; export const AmendPermit = () => { - const { permitId, companyId } = useParams(); + const { + permitId: permitIdParam, + companyId: companyIdParam, + } = useParams(); + + const companyId: number = applyWhenNotNullable(id => Number(id), companyIdParam, 0); + const permitId = getDefaultRequiredVal("", permitIdParam); + const { idirUserDetails } = useContext(OnRouteBCContext); const navigate = useNavigate(); @@ -82,16 +88,16 @@ export const AmendPermit = () => { const { data: permit } = usePermitDetailsQuery(companyId, permitId); // Get original permit id for the permit - const originalPermitId = permit?.originalPermitId; + const originalPermitId = getDefaultRequiredVal("", permit?.originalPermitId); // Get permit history for original permit id - const permitHistoryQuery = usePermitHistoryQuery(originalPermitId, companyId); + const permitHistoryQuery = usePermitHistoryQuery(companyId, originalPermitId); const permitHistory = getDefaultRequiredVal([], permitHistoryQuery.data); // Get latest amendment application for the permit, if any const { data: latestAmendmentApplication } = useAmendmentApplicationQuery( - originalPermitId, companyId, + originalPermitId, ); const isLoadingState = () => { diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx index 28cbe8d59..db492736e 100644 --- a/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx +++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitFinish.tsx @@ -12,12 +12,13 @@ import { useIssuePermits, useStartTransaction } from "../../../hooks/hooks"; import { isValidTransaction } from "../../../helpers/payment"; import { hasPermitsActionFailed } from "../../../helpers/permitState"; import { ERROR_ROUTES } from "../../../../../routes/constants"; -import { getDefaultRequiredVal } from "../../../../../common/helpers/util"; +import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../../common/helpers/util"; import { DEFAULT_PERMIT_TYPE } from "../../../types/PermitType"; export const AmendPermitFinish = () => { const navigate = useNavigate(); - const { companyId } = useParams(); + const { companyId: companyIdParam } = useParams(); + const companyId: number = applyWhenNotNullable(id => Number(id), companyIdParam, 0); const { permit, @@ -52,7 +53,7 @@ export const AmendPermitFinish = () => { useStartTransaction(); const { mutation: issuePermitMutation, issueResults } = - useIssuePermits(companyId); + useIssuePermits(); useEffect(() => { if (typeof transaction !== "undefined") { @@ -63,10 +64,13 @@ export const AmendPermitFinish = () => { navigate(ERROR_ROUTES.UNEXPECTED); } else { // refund transaction successful, proceed to issue permit - issuePermitMutation.mutate([permitId]); + issuePermitMutation.mutate({ + companyId, + applicationIds: [permitId], + }); } } - }, [transaction]); + }, [transaction, permitId, companyId]); useEffect(() => { const issueFailed = hasPermitsActionFailed(issueResults); diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx index 504ad6b79..eb59e5c27 100644 --- a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx +++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx @@ -15,7 +15,7 @@ import { AmendRevisionHistory } from "./form/AmendRevisionHistory"; import { AmendReason } from "./form/AmendReason"; import { Nullable } from "../../../../../common/types/common"; import { ERROR_ROUTES } from "../../../../../routes/constants"; -import { getDefaultRequiredVal } from "../../../../../common/helpers/util"; +import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../../common/helpers/util"; import { PermitVehicleDetails } from "../../../types/PermitVehicleDetails"; import { AmendPermitFormData } from "../types/AmendPermitFormData"; import { getDatetimes } from "./helpers/getDatetimes"; @@ -51,7 +51,7 @@ export const AmendPermitForm = () => { } = useContext(AmendPermitContext); const { companyId: companyIdParam } = useParams(); - const companyId = getDefaultRequiredVal("", companyIdParam); + const companyId: number = applyWhenNotNullable(id => Number(id), companyIdParam, 0); const navigate = useNavigate(); const { data: companyInfo } = useCompanyInfoDetailsQuery(companyId); diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx index 6819643fd..1448d1e60 100644 --- a/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx +++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx @@ -16,6 +16,7 @@ import { ERROR_ROUTES } from "../../../../../routes/constants"; import { DEFAULT_PERMIT_TYPE } from "../../../types/PermitType"; import { usePowerUnitSubTypesQuery } from "../../../../manageVehicles/hooks/powerUnits"; import { useTrailerSubTypesQuery } from "../../../../manageVehicles/hooks/trailers"; +import { PERMIT_REVIEW_CONTEXTS } from "../../../types/PermitReviewContext"; import { applyWhenNotNullable, getDefaultRequiredVal, @@ -23,7 +24,8 @@ import { export const AmendPermitReview = () => { const navigate = useNavigate(); - const { companyId } = useParams(); + const { companyId: companyIdParam } = useParams(); + const companyId: number = applyWhenNotNullable(id => Number(id), companyIdParam, 0); const { permit, @@ -33,13 +35,15 @@ export const AmendPermitReview = () => { back, next, getLinks, - } = - useContext(AmendPermitContext); + } = useContext(AmendPermitContext); // Send data to the backend API const modifyAmendmentMutation = useModifyAmendmentApplication(); - const { createdDateTime, updatedDateTime } = getDatetimes(amendmentApplication, permit); + const { createdDateTime, updatedDateTime } = getDatetimes( + amendmentApplication, + permit, + ); const validTransactionHistory = permitHistory.filter((history) => isValidTransaction(history.paymentMethodTypeCode, history.pgApproved), @@ -52,31 +56,32 @@ export const AmendPermitReview = () => { const trailerSubTypesQuery = useTrailerSubTypesQuery(); // For the confirmation checkboxes - const [isChecked, setIsChecked] = useState(false); - const [isSubmitted, setIsSubmitted] = useState(false); + const [allConfirmed, setAllConfirmed] = useState(false); + const [hasAttemptedSubmission, setHasAttemptedSubmission] = useState(false); const onSubmit = async () => { - setIsSubmitted(true); - if (!isChecked) return; + setHasAttemptedSubmission(true); + if (!allConfirmed) return; if (!amendmentApplication) { return navigate(ERROR_ROUTES.UNEXPECTED); } - const { application: savedApplication } = await modifyAmendmentMutation.mutateAsync({ - applicationId: getDefaultRequiredVal( - "", - amendmentApplication?.permitId, - ), - application: { - ...amendmentApplication, - permitData: { - ...amendmentApplication.permitData, - doingBusinessAs, // always set most recent company info DBA + const { application: savedApplication } = + await modifyAmendmentMutation.mutateAsync({ + applicationId: getDefaultRequiredVal( + "", + amendmentApplication?.permitId, + ), + application: { + ...amendmentApplication, + permitData: { + ...amendmentApplication.permitData, + doingBusinessAs, // always set most recent company info DBA + }, }, - }, - companyId: companyId as string, - }); + companyId, + }); if (savedApplication) { setAmendmentApplication(savedApplication); @@ -112,6 +117,7 @@ export const AmendPermitReview = () => { { continueBtnText="Continue" onEdit={back} onContinue={onSubmit} - allChecked={isChecked} - setAllChecked={setIsChecked} - hasAttemptedCheckboxes={isSubmitted} + allConfirmed={allConfirmed} + setAllConfirmed={setAllConfirmed} + hasAttemptedCheckboxes={hasAttemptedSubmission} powerUnitSubTypes={powerUnitSubTypesQuery.data} trailerSubTypes={trailerSubTypesQuery.data} vehicleDetails={amendmentApplication?.permitData?.vehicleDetails} diff --git a/frontend/src/features/permits/pages/Application/ApplicationForm.tsx b/frontend/src/features/permits/pages/Application/ApplicationForm.tsx index a12a20b66..0b3f7ac88 100644 --- a/frontend/src/features/permits/pages/Application/ApplicationForm.tsx +++ b/frontend/src/features/permits/pages/Application/ApplicationForm.tsx @@ -57,11 +57,11 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => { const companyInfo = companyInfoQuery.data; // Company id should be set by context, otherwise default to companyId in session and then the fetched companyId - const companyId = getDefaultRequiredVal( - "", - applyWhenNotNullable((id) => `${id}`, companyIdFromContext), - getCompanyIdFromSession(), - applyWhenNotNullable((id) => `${id}`, companyInfo?.companyId), + const companyId: number = getDefaultRequiredVal( + 0, + companyIdFromContext, + applyWhenNotNullable(id => Number(id), getCompanyIdFromSession()), + companyInfo?.companyId, ); const { data: specialAuthorizations } = useFetchSpecialAuthorizations(companyId); @@ -206,7 +206,10 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => { ); const { application: savedApplication, status } = - await saveApplicationMutation.mutateAsync(applicationToBeSaved); + await saveApplicationMutation.mutateAsync({ + data: applicationToBeSaved, + companyId, + }); if (savedApplication) { const savedPermitId = onSaveSuccess(savedApplication, status); diff --git a/frontend/src/features/permits/pages/Application/ApplicationReview.tsx b/frontend/src/features/permits/pages/Application/ApplicationReview.tsx index e6e04bbda..62e03eb49 100644 --- a/frontend/src/features/permits/pages/Application/ApplicationReview.tsx +++ b/frontend/src/features/permits/pages/Application/ApplicationReview.tsx @@ -9,7 +9,7 @@ import { useSaveApplicationMutation } from "../../hooks/hooks"; import { ApplicationBreadcrumb } from "../../components/application-breadcrumb/ApplicationBreadcrumb"; import { useCompanyInfoQuery } from "../../../manageProfile/apiManager/hooks"; import { PermitReview } from "./components/review/PermitReview"; -import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { getDefaultRequiredVal } from "../../../../common/helpers/util"; import { SnackBarContext } from "../../../../App"; import { useAddToCart } from "../../hooks/cart"; import { hasPermitsActionFailed } from "../../helpers/permitState"; @@ -20,6 +20,7 @@ import { useFetchSpecialAuthorizations } from "../../../settings/hooks/specialAu import { applyLCVToApplicationData } from "../../helpers/getDefaultApplicationFormData"; import { calculateFeeByDuration } from "../../helpers/feeSummary"; import { DEFAULT_PERMIT_TYPE } from "../../types/PermitType"; +import { PERMIT_REVIEW_CONTEXTS } from "../../types/PermitReviewContext"; import { APPLICATIONS_ROUTES, APPLICATION_STEPS, @@ -32,11 +33,7 @@ export const ApplicationReview = () => { setApplicationData: setApplicationContextData, } = useContext(ApplicationContext); - const companyId = applyWhenNotNullable( - id => `${id}`, - applicationContextData?.companyId, - "", - ) as string; + const companyId = getDefaultRequiredVal(0, applicationContextData?.companyId); const { data: specialAuth } = useFetchSpecialAuthorizations(companyId); const isLcvDesignated = Boolean(specialAuth?.isLcvAllowed); @@ -44,7 +41,7 @@ export const ApplicationReview = () => { const { data: companyInfo } = useCompanyInfoQuery(); const doingBusinessAs = companyInfo?.alternateName; - + const applicationData = applyLCVToApplicationData( applicationContextData, isLcvDesignated, @@ -53,9 +50,9 @@ export const ApplicationReview = () => { const fee = isNoFeePermitType ? "0" : `${calculateFeeByDuration( - getDefaultRequiredVal(DEFAULT_PERMIT_TYPE, applicationData?.permitType), - getDefaultRequiredVal(0, applicationData?.permitData?.permitDuration), - )}`; + getDefaultRequiredVal(DEFAULT_PERMIT_TYPE, applicationData?.permitType), + getDefaultRequiredVal(0, applicationData?.permitData?.permitDuration), + )}`; const { setSnackBar } = useContext(SnackBarContext); const { refetchCartCount } = useContext(CartContext); @@ -70,8 +67,8 @@ export const ApplicationReview = () => { const methods = useForm(); // For the confirmation checkboxes - const [isChecked, setIsChecked] = useState(false); - const [isSubmitted, setIsSubmitted] = useState(false); + const [allConfirmed, setAllConfirmed] = useState(false); + const [hasAttemptedSubmission, setHasAttemptedSubmission] = useState(false); // Send data to the backend API const saveApplicationMutation = useSaveApplicationMutation(); @@ -82,7 +79,7 @@ export const ApplicationReview = () => { }; const proceedWithAddToCart = async ( - companyId: string, + companyId: number, applicationIds: string[], onSuccess: () => void, ) => { @@ -99,9 +96,9 @@ export const ApplicationReview = () => { }; const handleAddToCart = async () => { - setIsSubmitted(true); + setHasAttemptedSubmission(true); - if (!isChecked) return; + if (!allConfirmed) return; const companyId = applicationData?.companyId; const permitId = applicationData?.permitId; @@ -112,31 +109,30 @@ export const ApplicationReview = () => { const { application: savedApplication } = await saveApplicationMutation.mutateAsync({ - ...applicationData, - permitData: { - ...applicationData.permitData, - doingBusinessAs, // always set most recent DBA from company info - } + data: { + ...applicationData, + permitData: { + ...applicationData.permitData, + doingBusinessAs, // always set most recent DBA from company info + }, + }, + companyId, }); if (savedApplication) { setApplicationContextData(savedApplication); - await proceedWithAddToCart( - `${companyId}`, - [permitId], - () => { - setSnackBar({ - showSnackbar: true, - setShowSnackbar: () => true, - message: `Application ${applicationNumber} added to cart`, - alertType: "success", - }); - - refetchCartCount(); - navigate(APPLICATIONS_ROUTES.BASE); - }, - ); + await proceedWithAddToCart(companyId, [permitId], () => { + setSnackBar({ + showSnackbar: true, + setShowSnackbar: () => true, + message: `Application ${applicationNumber} added to cart`, + alertType: "success", + }); + + refetchCartCount(); + navigate(APPLICATIONS_ROUTES.BASE); + }); } else { navigate(ERROR_ROUTES.UNEXPECTED); } @@ -155,6 +151,7 @@ export const ApplicationReview = () => { { contactDetails={applicationData?.permitData?.contactDetails} onEdit={back} onAddToCart={handleAddToCart} - allChecked={isChecked} - setAllChecked={setIsChecked} - hasAttemptedCheckboxes={isSubmitted} + allConfirmed={allConfirmed} + setAllConfirmed={setAllConfirmed} + hasAttemptedCheckboxes={hasAttemptedSubmission} powerUnitSubTypes={powerUnitSubTypesQuery.data} trailerSubTypes={trailerSubTypesQuery.data} vehicleDetails={applicationData?.permitData?.vehicleDetails} diff --git a/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.scss b/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.scss index 6e17915c3..d8f7a6d9d 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.scss +++ b/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.scss @@ -6,10 +6,17 @@ & &__checkbox { padding-left: 0; color: $bc-primary-blue; - + + &--invalid { color: $bc-red; } + + &--checked { + &.confirmation-checkboxes__checkbox--disabled { + color: $disabled-colour; + } + } } & &__attestation { @@ -21,4 +28,4 @@ & &__error { color: $bc-red; } -} +} \ No newline at end of file diff --git a/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.tsx b/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.tsx index b479f2f52..dae7ee698 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ConfirmationCheckboxes.tsx @@ -1,47 +1,57 @@ import { Box, Checkbox, Typography } from "@mui/material"; -import { Dispatch, SetStateAction, useState } from "react"; +import { useState } from "react"; import "./ConfirmationCheckboxes.scss"; import { CustomInputHTMLAttributes } from "../../../../../../common/types/formElements"; export const ConfirmationCheckboxes = ({ - isSubmitted, - isChecked, - setIsChecked, + hasAttemptedSubmission, + areAllChecked, + setAreAllChecked, + shouldDisableCheckboxes, }: { - isSubmitted: boolean; - isChecked: boolean; - setIsChecked: Dispatch>; + hasAttemptedSubmission: boolean; + areAllChecked: boolean; + setAreAllChecked: (allChecked: boolean) => void; + shouldDisableCheckboxes: boolean; }) => { const checkboxes = [ { description: "Confirm that this permit is issued to the registered owner (or lessee) of the vehicle being permitted.", - checked: false, + checked: shouldDisableCheckboxes, }, { description: "Confirm compliance with the appropriate policy for the selected commodity(s).", - checked: false, + checked: shouldDisableCheckboxes, }, { description: "Confirm the information in this application is correct.", - checked: false, + checked: shouldDisableCheckboxes, }, ]; - const [checked, setChecked] = useState(checkboxes); + + const [confirmationCheckboxes, setConfirmationCheckboxes] = + useState(checkboxes); const handleCheck = (checkedName: string) => { - let isValid = true; - const updated = checked.map((item) => { + if (shouldDisableCheckboxes) return; + + const updatedCheckboxes = confirmationCheckboxes.map((item) => { + if (shouldDisableCheckboxes) return item; + if (item.description === checkedName) { - item.checked = !item.checked; + return { + description: item.description, + checked: !item.checked, + }; } - if (!item.checked) isValid = false; + return item; }); - setChecked(updated); - setIsChecked(isValid); + setConfirmationCheckboxes(updatedCheckboxes); + setAreAllChecked(!updatedCheckboxes.some((updated) => !updated.checked)); }; return ( @@ -49,33 +59,38 @@ export const ConfirmationCheckboxes = ({ Please read the following carefully and check all to proceed. - {checked.map((x) => ( - + + {confirmationCheckboxes.map(({ description, checked }) => ( + handleCheck(x.description)} + classes={{ + root: "confirmation-checkboxes__checkbox", + disabled: "confirmation-checkboxes__checkbox--disabled", + checked: "confirmation-checkboxes__checkbox--checked", + }} + key={description} + checked={checked} + disabled={shouldDisableCheckboxes} + onChange={() => handleCheck(description)} inputProps={ { "data-testid": "permit-attestation-checkbox", } as CustomInputHTMLAttributes } /> - {x.description} + {description} ))} - {isSubmitted && !isChecked ? ( + + {hasAttemptedSubmission && !areAllChecked ? ( ; permitNumber?: Nullable; applicationNumber?: Nullable; @@ -35,8 +39,8 @@ interface PermitReviewProps { isAmendAction: boolean; children?: React.ReactNode; hasAttemptedCheckboxes: boolean; - allChecked: boolean; - setAllChecked: Dispatch>; + allConfirmed: boolean; + setAllConfirmed: (confirmed: boolean) => void; powerUnitSubTypes?: Nullable; trailerSubTypes?: Nullable; vehicleDetails?: Nullable; @@ -44,6 +48,10 @@ interface PermitReviewProps { onEdit: () => void; onContinue?: () => Promise; onAddToCart?: () => Promise; + onApprove?: () => Promise; + approveApplicationMutationPending?: boolean; + onReject?: () => Promise; + rejectApplicationMutationPending?: boolean; showChangedFields?: boolean; oldFields?: Nullable>; calculatedFee: string; @@ -95,11 +103,12 @@ export const PermitReview = (props: PermitReviewProps) => { /> {props.children} @@ -108,8 +117,16 @@ export const PermitReview = (props: PermitReviewProps) => { onEdit={props.onEdit} continueBtnText={props.continueBtnText} onContinue={props.onContinue} - hasToCartButton={!props.isAmendAction} + hasToCartButton={props.reviewContext === PERMIT_REVIEW_CONTEXTS.APPLY} onAddToCart={props.onAddToCart} + onApprove={props.onApprove} + approveApplicationMutationPending={ + props.approveApplicationMutationPending + } + onReject={props.onReject} + rejectApplicationMutationPending={ + props.rejectApplicationMutationPending + } /> diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.scss b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.scss index ebdba3080..c6363f277 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.scss +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.scss @@ -6,8 +6,13 @@ display: flex; align-items: center; justify-content: flex-end; + gap: 2.5rem; & &__btn { + box-shadow: none; + &:hover { + box-shadow: none; + } .button-icon { margin-right: 0.5rem; } @@ -17,10 +22,27 @@ align-items: center; } + &--reject { + &:hover { + background-color: $bc-messages-red-text; + } + &:disabled { + background-color: $bc-red-disabled; + color: $white; + } + } + + &--approve { + &:disabled { + background-color: $disabled-colour; + color: $white; + } + } + &--cart { padding-left: 2rem; padding-right: 2rem; - margin-left: 2.5rem; + font-weight: bold; border-color: $bc-primary-blue; color: $bc-primary-blue; @@ -34,7 +56,6 @@ &--continue { font-weight: bold; - margin-left: 2.5rem; } } } diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx index b82d7dcc0..3f705003f 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx @@ -1,7 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { Box, Button } from "@mui/material"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPencil } from "@fortawesome/free-solid-svg-icons"; - import "./ReviewActions.scss"; export const ReviewActions = ({ @@ -10,16 +10,25 @@ export const ReviewActions = ({ onContinue, hasToCartButton, onAddToCart, + onApprove, + approveApplicationMutationPending, + onReject, + rejectApplicationMutationPending, }: { onEdit: () => void; continueBtnText?: string; onContinue?: () => Promise; hasToCartButton: boolean; onAddToCart?: () => Promise; + onApprove?: () => Promise; + approveApplicationMutationPending?: boolean; + onReject?: () => Promise; + rejectApplicationMutationPending?: boolean; }) => { return ( - + */} {hasToCartButton ? ( + ) : null} + + {onApprove ? ( + + ) : null} ); }; diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewFeeSummary.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewFeeSummary.tsx index f758ef016..93bf721a3 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewFeeSummary.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewFeeSummary.tsx @@ -1,24 +1,29 @@ import { Box, Typography } from "@mui/material"; -import { Dispatch, SetStateAction } from "react"; import "./ReviewFeeSummary.scss"; import { ConfirmationCheckboxes } from "./ConfirmationCheckboxes"; import { FeeSummary } from "../../../../components/feeSummary/FeeSummary"; import { PermitType } from "../../../../types/PermitType"; import { Nullable } from "../../../../../../common/types/common"; +import { + PERMIT_REVIEW_CONTEXTS, + PermitReviewContext, +} from "../../../../types/PermitReviewContext"; export const ReviewFeeSummary = ({ - isSubmitted, - isChecked, - setIsChecked, + hasAttemptedSubmission, + areAllConfirmed, + setAreAllConfirmed, permitType, fee, + reviewContext, }: { - isSubmitted: boolean; - isChecked: boolean; - setIsChecked: Dispatch>; + hasAttemptedSubmission: boolean; + areAllConfirmed: boolean; + setAreAllConfirmed: (allConfirmed: boolean) => void; permitType?: Nullable; fee: string; + reviewContext: PermitReviewContext; }) => { return ( @@ -30,9 +35,12 @@ export const ReviewFeeSummary = ({ diff --git a/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx b/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx index f5221ae86..b3b5b3f76 100644 --- a/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx +++ b/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx @@ -98,7 +98,7 @@ const server = setupServer( ), http.put( - `${APPLICATIONS_API_ROUTES.UPDATE(companyInfo.companyId.toString())}/:id`, + APPLICATIONS_API_ROUTES.UPDATE(companyInfo.companyId.toString(), ":id"), async ({ request, params }) => { const { id } = params; const reqBody = await request.json(); diff --git a/frontend/src/features/permits/pages/Payment/PaymentRedirect.tsx b/frontend/src/features/permits/pages/Payment/PaymentRedirect.tsx index f7e4de2d2..46b6f10ef 100644 --- a/frontend/src/features/permits/pages/Payment/PaymentRedirect.tsx +++ b/frontend/src/features/permits/pages/Payment/PaymentRedirect.tsx @@ -1,16 +1,20 @@ import { useContext, useEffect, useRef } from "react"; import { Navigate, useNavigate, useSearchParams } from "react-router-dom"; -import { - getPayBCPaymentDetails, - usePaymentByTransactionIdQuery, -} from "../../helpers/payment"; import { Loading } from "../../../../common/pages/Loading"; import { useCompleteTransaction, useIssuePermits } from "../../hooks/hooks"; -import { getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../common/helpers/util"; import { DATE_FORMATS, toUtc } from "../../../../common/helpers/formatDate"; import { hasPermitsActionFailed } from "../../helpers/permitState"; import { PaymentCardTypeCode } from "../../../../common/types/paymentMethods"; +import { useAddToCart } from "../../hooks/cart"; +import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; +import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler"; +import { + getPayBCPaymentDetails, + usePaymentByTransactionIdQuery, +} from "../../helpers/payment"; + import { ERROR_ROUTES, PERMITS_ROUTES, @@ -21,8 +25,6 @@ import { CompleteTransactionRequestData, PayBCPaymentDetails, } from "../../types/payment"; -import { useAddToCart } from "../../hooks/cart"; -import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; /** * React component that handles the payment redirect and displays the payment status. @@ -32,7 +34,13 @@ import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext export const PaymentRedirect = () => { const navigate = useNavigate(); const completedTransaction = useRef(false); - const { companyId } = useContext(OnRouteBCContext); + const { companyId: companyIdFromContext } = useContext(OnRouteBCContext); + const companyId: number = getDefaultRequiredVal( + 0, + companyIdFromContext, + applyWhenNotNullable(id => Number(id), getCompanyIdFromSession()), + ); + const issuedPermit = useRef(false); const [searchParams] = useSearchParams(); const paymentDetails = getPayBCPaymentDetails(searchParams); @@ -73,14 +81,17 @@ export const PaymentRedirect = () => { if (paymentApproved === true) { // Payment successful, proceed to issue permit - issuePermitsMutation.mutate(applicationIds); + issuePermitsMutation.mutate({ + companyId, + applicationIds, + }); issuedPermit.current = true; } else if (paymentApproved === false) { // Add back to cart and then redirect to shopping cart. - if (!addToCartMutation.isPending && addToCartMutation.isIdle) { + if (!addToCartMutation.isPending && addToCartMutation.isIdle && companyId) { addToCartMutation .mutateAsync({ - companyId: `${companyId}`, + companyId, applicationIds, }) .then(({ failure }) => { @@ -103,7 +114,7 @@ export const PaymentRedirect = () => { if (transactionIdQuery?.isError) navigate(ERROR_ROUTES.UNEXPECTED, { replace: true }); - }, [paymentApproved, transactionIdQuery]); + }, [paymentApproved, transactionIdQuery, companyId]); if (issueFailed) { return ; diff --git a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx index e1d9d80e8..229e2ee1d 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx +++ b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx @@ -2,6 +2,7 @@ import { Box } from "@mui/material"; import { useContext, useEffect } from "react"; import { useSearchParams, useNavigate, Navigate } from "react-router-dom"; import { FormProvider, useForm } from "react-hook-form"; + import "./ShoppingCartPage.scss"; import { ApplicationContext } from "../../context/ApplicationContext"; import { isZeroAmount } from "../../helpers/feeSummary"; @@ -9,12 +10,23 @@ import { PermitPayFeeSummary } from "../Application/components/pay/PermitPayFeeS import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; import { useIssuePermits, useStartTransaction } from "../../hooks/hooks"; import { TRANSACTION_TYPES } from "../../types/payment"; +import { PaymentFailedBanner } from "../Application/components/pay/PaymentFailedBanner"; +import { ChoosePaymentMethod } from "../Application/components/pay/ChoosePaymentMethod"; +import { hasPermitsActionFailed } from "../../helpers/permitState"; +import { ShoppingCart } from "./components/ShoppingCart"; +import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler"; +import { useShoppingCart } from "./hooks/useShoppingCart"; +import { useCheckOutdatedCart } from "./hooks/useCheckOutdatedCart"; +import { EditCartItemDialog } from "../../components/cart/EditCartItemDialog"; +import { UpdateCartDialog } from "../../components/cart/UpdateCartDialog"; +import { BCeID_USER_ROLE } from "../../../../common/authentication/types"; +import { Loading } from "../../../../common/pages/Loading"; +import { CVPayInPersonInfo } from "../Application/components/pay/CVPayInPersonInfo"; import { PAYMENT_METHOD_TYPE_CODE, PaymentCardTypeCode, } from "../../../../common/types/paymentMethods"; -import { PaymentFailedBanner } from "../Application/components/pay/PaymentFailedBanner"; -import { ChoosePaymentMethod } from "../Application/components/pay/ChoosePaymentMethod"; + import { DEFAULT_EMPTY_CARD_TYPE, DEFAULT_EMPTY_PAYMENT_TYPE, @@ -26,14 +38,7 @@ import { isCashOrCheque, PaymentMethodData, } from "../Application/components/pay/types/PaymentMethodData"; -import { hasPermitsActionFailed } from "../../helpers/permitState"; -import { ShoppingCart } from "./components/ShoppingCart"; -import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler"; -import { useShoppingCart } from "./hooks/useShoppingCart"; -import { useCheckOutdatedCart } from "./hooks/useCheckOutdatedCart"; -import { EditCartItemDialog } from "../../components/cart/EditCartItemDialog"; -import { UpdateCartDialog } from "../../components/cart/UpdateCartDialog"; -import { BCeID_USER_ROLE } from "../../../../common/authentication/types"; + import { applyWhenNotNullable, getDefaultRequiredVal, @@ -45,8 +50,7 @@ import { PERMITS_ROUTES, SHOPPING_CART_ROUTES, } from "../../../../routes/constants"; -import { Loading } from "../../../../common/pages/Loading"; -import { CVPayInPersonInfo } from "../Application/components/pay/CVPayInPersonInfo"; + import { TOLL_FREE_NUMBER, PPC_EMAIL, @@ -65,7 +69,7 @@ export const ShoppingCartPage = () => { const navigate = useNavigate(); const { applicationData } = useContext(ApplicationContext); const { idirUserDetails, userDetails } = useContext(OnRouteBCContext); - const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); + const companyId: number = applyWhenNotNullable(id => Number(id), getCompanyIdFromSession(), 0); const isStaffActingAsCompany = Boolean(idirUserDetails?.userRole); const isCompanyAdmin = Boolean( userDetails?.userRole === BCeID_USER_ROLE.COMPANY_ADMINISTRATOR, @@ -109,7 +113,7 @@ export const ShoppingCartPage = () => { fetchStatusFor, setShowEditCartItemDialog, setShowUpdateCartDialog, - } = useCheckOutdatedCart(showAllApplications, cartItems); + } = useCheckOutdatedCart(companyId, showAllApplications, cartItems); const { mutation: startTransactionMutation, transaction } = useStartTransaction(); @@ -150,7 +154,10 @@ export const ShoppingCartPage = () => { } else if (isFeeZero || isStaffActingAsCompany) { // If purchase was for no-fee permits, or if staff payment transaction was created successfully, // simply proceed to issue permits - issuePermitMutation.mutate([...selectedIds]); + issuePermitMutation.mutate({ + companyId, + applicationIds: [...selectedIds], + }); // also update the cart and cart count cartQuery.refetch(); @@ -166,7 +173,7 @@ export const ShoppingCartPage = () => { } } } - }, [transaction, isStaffActingAsCompany, isFeeZero]); + }, [transaction, isStaffActingAsCompany, isFeeZero, companyId]); useEffect(() => { const issueFailed = hasPermitsActionFailed(issueResults); diff --git a/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts b/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts index a3d54577e..aa5d0ef91 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts +++ b/frontend/src/features/permits/pages/ShoppingCart/hooks/useCheckOutdatedCart.ts @@ -7,6 +7,7 @@ import { getOutdatedCartItems } from "../../../helpers/cart"; import { getDefaultRequiredVal } from "../../../../../common/helpers/util"; export const useCheckOutdatedCart = ( + companyId: number, cartFilterChanged: boolean, fetchedCartItems?: CartItem[], ) => { @@ -18,7 +19,7 @@ export const useCheckOutdatedCart = ( cartItemId: idOfCartItemToEdit, cartItemData: cartItemToEdit, fetchStatusFor, - } = useFetchCartItemStatus(); + } = useFetchCartItemStatus(companyId); useEffect(() => { // Reset old cart items whenever radio button filter is changed diff --git a/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts b/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts index 652026301..cf7bdae31 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts +++ b/frontend/src/features/permits/pages/ShoppingCart/hooks/useShoppingCart.ts @@ -8,7 +8,7 @@ import { useFetchSpecialAuthorizations } from "../../../../settings/hooks/specia import { calculateFeeByDuration } from "../../../helpers/feeSummary"; export const useShoppingCart = ( - companyId: string, + companyId: number, enableCartFilter: boolean, ) => { const { refetchCartCount } = useContext(CartContext); diff --git a/frontend/src/features/permits/pages/Void/FinishVoid.tsx b/frontend/src/features/permits/pages/Void/FinishVoid.tsx index 608a72907..7e45a5801 100644 --- a/frontend/src/features/permits/pages/Void/FinishVoid.tsx +++ b/frontend/src/features/permits/pages/Void/FinishVoid.tsx @@ -1,4 +1,5 @@ import { useContext, useEffect } from "react"; +import { useParams } from "react-router-dom"; import { VoidPermitContext } from "./context/VoidPermitContext"; import { RefundFormData } from "../Refund/types/RefundFormData"; @@ -23,15 +24,20 @@ export const FinishVoid = ({ onFail: () => void; }) => { const { voidPermitData } = useContext(VoidPermitContext); + const { companyId: companyIdParam } = useParams(); const { email, additionalEmail, fax, reason } = voidPermitData; + const companyId: number = getDefaultRequiredVal( + 0, + permit?.companyId, + applyWhenNotNullable(id => Number(id), companyIdParam), + ); + + const originalPermitId = getDefaultRequiredVal("", permit?.originalPermitId); const permitHistoryQuery = usePermitHistoryQuery( - permit?.originalPermitId, - applyWhenNotNullable( - id => `${id}`, - permit?.companyId, - ), + companyId, + originalPermitId, ); const permitHistory = getDefaultRequiredVal([], permitHistoryQuery.data); diff --git a/frontend/src/features/permits/pages/Void/VoidPermit.tsx b/frontend/src/features/permits/pages/Void/VoidPermit.tsx index 8ebbaa37b..43d56a7d0 100644 --- a/frontend/src/features/permits/pages/Void/VoidPermit.tsx +++ b/frontend/src/features/permits/pages/Void/VoidPermit.tsx @@ -14,7 +14,7 @@ import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext import { USER_ROLE } from "../../../../common/authentication/types"; import { isPermitInactive } from "../../types/PermitStatus"; import { Permit } from "../../types/permit"; -import { getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../common/helpers/util"; import { Breadcrumb } from "../../../../common/components/breadcrumb/Breadcrumb"; import { hasPermitExpired } from "../../helpers/permitState"; import { @@ -35,7 +35,13 @@ const isVoidable = (permit: Permit) => { export const VoidPermit = () => { const navigate = useNavigate(); - const { permitId, companyId } = useParams(); + const { + permitId: permitIdParam, + companyId: companyIdParam, + } = useParams(); + + const companyId: number = applyWhenNotNullable(id => Number(id), companyIdParam, 0); + const permitId = getDefaultRequiredVal("", permitIdParam); const [currentLink, setCurrentLink] = useState(0); const getBannerText = () => currentLink === 0 ? "Void Permit" : "Finish Voiding"; diff --git a/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx b/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx index 68128c983..1dcc233c8 100644 --- a/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx +++ b/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx @@ -2,6 +2,7 @@ import { Controller, FormProvider } from "react-hook-form"; import isEmail from "validator/lib/isEmail"; import { Button, FormControl, FormHelperText } from "@mui/material"; import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; import "./VoidPermitForm.scss"; import { useVoidPermitForm } from "../hooks/useVoidPermitForm"; @@ -47,13 +48,19 @@ export const VoidPermitForm = ({ useVoidPermitForm(); const { mutation: revokePermitMutation, voidResults } = useVoidPermit(); + const { companyId: companyIdParam } = useParams(); + + const companyId: number = getDefaultRequiredVal( + 0, + permit?.companyId, + applyWhenNotNullable(id => Number(id), companyIdParam), + ); + + const originalPermitId = getDefaultRequiredVal("", permit?.originalPermitId); const { data: permitHistory } = usePermitHistoryQuery( - permit?.originalPermitId, - applyWhenNotNullable( - id => `${id}`, - permit?.companyId, - ), + companyId, + originalPermitId, ); const transactionHistory = getDefaultRequiredVal([], permitHistory) diff --git a/frontend/src/features/permits/types/PermitReviewContext.ts b/frontend/src/features/permits/types/PermitReviewContext.ts new file mode 100644 index 000000000..2e9b4005a --- /dev/null +++ b/frontend/src/features/permits/types/PermitReviewContext.ts @@ -0,0 +1,8 @@ +export const PERMIT_REVIEW_CONTEXTS = { + APPLY: "APPLY", + AMEND: "AMEND", + QUEUE: "QUEUE", +} as const; + +export type PermitReviewContext = + (typeof PERMIT_REVIEW_CONTEXTS)[keyof typeof PERMIT_REVIEW_CONTEXTS]; diff --git a/frontend/src/features/permits/types/application.ts b/frontend/src/features/permits/types/application.ts index 102fb5992..fb4a0314c 100644 --- a/frontend/src/features/permits/types/application.ts +++ b/frontend/src/features/permits/types/application.ts @@ -7,7 +7,7 @@ import { Nullable } from "../../../common/types/common"; import { PermitApplicationOrigin } from "./PermitApplicationOrigin"; import { PermitApprovalSource } from "./PermitApprovalSource"; import { PermitData } from "./PermitData"; -import { ApplicationQueueStatus } from "./ApplicationQueueStatus"; +import { ApplicationQueueStatus } from "../../queue/types/ApplicationQueueStatus"; /** * A partial permit type that consists of all common fields used for a permit. @@ -108,6 +108,8 @@ export interface ApplicationListItem { vin?: Nullable; plate?: Nullable; applicationQueueStatus?: ApplicationQueueStatus; + timeInQueue?: string; + claimedBy?: string; } /** @@ -123,3 +125,14 @@ export interface ApplicationFormData { permitNumber?: Nullable; permitData: PermitData; } + +/** + * Type used for determining query parameters when fetching applications + */ +export interface ApplicationFilters { + pendingPermitsOnly?: boolean; + applicationsInQueueOnly?: boolean; + claimedApplicationsOnly?: boolean; + unclaimedApplicationsOnly?: boolean; + getStaffQueue?: boolean; +} diff --git a/frontend/src/features/queue/apiManager/endpoints/endpoints.ts b/frontend/src/features/queue/apiManager/endpoints/endpoints.ts new file mode 100644 index 000000000..06be84c57 --- /dev/null +++ b/frontend/src/features/queue/apiManager/endpoints/endpoints.ts @@ -0,0 +1,11 @@ +import { VEHICLES_URL } from "../../../../common/apiManager/endpoints/endpoints"; + +const APPLICATIONS_API_BASE = (companyId: number) => + `${VEHICLES_URL}/companies/${companyId}/applications`; + +export const APPLICATION_QUEUE_API_ROUTES = { + UPDATE_QUEUE_STATUS: (companyId: number, applicationId: string) => + `${APPLICATIONS_API_BASE(companyId)}/${applicationId}/queue/status`, + CLAIM: (companyId: number, applicationId: string) => + `${APPLICATIONS_API_BASE(companyId)}/${applicationId}/queue/assign`, +}; diff --git a/frontend/src/features/queue/apiManager/queueAPI.ts b/frontend/src/features/queue/apiManager/queueAPI.ts new file mode 100644 index 000000000..0415d85dc --- /dev/null +++ b/frontend/src/features/queue/apiManager/queueAPI.ts @@ -0,0 +1,106 @@ +import { + getCompanyIdFromSession, + httpPOSTRequest, +} from "../../../common/apiManager/httpRequestHandler"; +import { getDefaultRequiredVal } from "../../../common/helpers/util"; +import { + Nullable, + PaginatedResponse, + PaginationAndFilters, +} from "../../../common/types/common"; +import { getApplications } from "../../permits/apiManager/permitsAPI"; +import { ApplicationListItem } from "../../permits/types/application"; +import { CaseActivityType } from "../types/CaseActivityType"; +import { APPLICATION_QUEUE_API_ROUTES } from "./endpoints/endpoints"; + +/** + * Fetch all applications in queue. + * @return A list of applications in queue (PENDING_REVIEW) + */ +export const getApplicationsInQueue = async ( + paginationFilters: PaginationAndFilters, + getStaffQueue: boolean, + companyId?: Nullable, +): Promise> => { + return await getApplications( + paginationFilters, + { + applicationsInQueueOnly: true, + getStaffQueue, + }, + companyId, + ); +}; + +/** + * Fetch all claimed applications in queue. + * @return A list of claimed applications in queue (IN_REVIEW) + */ +export const getClaimedApplicationsInQueue = async ( + paginationFilters: PaginationAndFilters, +): Promise> => { + return await getApplications(paginationFilters, { + getStaffQueue: true, + claimedApplicationsOnly: true, + }); +}; + +/** + * Fetch all unclaimed applications in queue. + * @return A list of claimed applications in queue (PENDING_REVIEW) + */ +export const getUnclaimedApplicationsInQueue = async ( + paginationFilters: PaginationAndFilters, +): Promise> => { + return await getApplications(paginationFilters, { + getStaffQueue: true, + unclaimedApplicationsOnly: true, + }); +}; + +export const updateApplicationQueueStatus = async ({ + applicationId, + caseActivityType, + companyId, + comment, +}: { + applicationId: Nullable; + caseActivityType: CaseActivityType; + companyId?: number; + comment?: string; +}) => { + companyId = getDefaultRequiredVal( + 0, + companyId, + Number(getCompanyIdFromSession()), + ); + applicationId = getDefaultRequiredVal("", applicationId); + + const data: any = { + caseActivityType, + }; + + // Conditionally include the comment property if it is given as an argument and not an empty string + if (comment && comment.trim() !== "") { + data.comment = [comment]; + } + + const response = await httpPOSTRequest( + APPLICATION_QUEUE_API_ROUTES.UPDATE_QUEUE_STATUS(companyId, applicationId), + data, + ); + return response; +}; + +export const claimApplicationInQueue = async ( + companyId: Nullable, + applicationId: Nullable, +) => { + companyId = getDefaultRequiredVal(0, companyId); + applicationId = getDefaultRequiredVal("", applicationId); + const response = await httpPOSTRequest( + APPLICATION_QUEUE_API_ROUTES.CLAIM(companyId, applicationId), + {}, + ); + return response; +}; diff --git a/frontend/src/features/queue/components/ApplicationInQueueColumnDefinition.tsx b/frontend/src/features/queue/components/ApplicationInQueueColumnDefinition.tsx new file mode 100644 index 000000000..90032add9 --- /dev/null +++ b/frontend/src/features/queue/components/ApplicationInQueueColumnDefinition.tsx @@ -0,0 +1,79 @@ +import { Box, Tooltip } from "@mui/material"; +import { MRT_ColumnDef } from "material-react-table"; +import { CustomNavLink } from "../../../common/components/links/CustomNavLink"; +import { APPLICATION_QUEUE_ROUTES } from "../../../routes/constants"; +import { ApplicationListItem } from "../../permits/types/application"; +import { getPermitTypeName } from "../../permits/types/PermitType"; + +export const getApplicationInQueueColumnDefinition = ( + handleFollowApplicationLink: (application: ApplicationListItem) => void, +): MRT_ColumnDef[] => [ + { + accessorKey: "applicationNumber", + id: "applicationNumber", + enableSorting: false, + header: "Application #", + accessorFn: (row) => row.applicationNumber, + Cell: (props: { cell: any; row: any }) => { + const application = props.row.original; + const permitId = application.permitId; + const companyId = application.companyId; + return ( + { + e.preventDefault(); + handleFollowApplicationLink(application); + }} + to={APPLICATION_QUEUE_ROUTES.REVIEW(companyId, permitId)} + className="column-link column-link--application-details" + > + {props.cell.getValue()} + + ); + }, + size: 200, + }, + { + accessorKey: "permitType", + id: "permitType", + enableSorting: false, + header: "Permit Type", + Cell: (props: { cell: any }) => { + const permitTypeName = getPermitTypeName(props.cell.getValue()); + return ( + + {props.cell.getValue()} + + ); + }, + size: 80, + }, + { + accessorKey: "plate", + id: "plate", + enableSorting: false, + header: "Plate", + size: 50, + }, + { + accessorKey: "legalName", + id: "legalName", + enableSorting: false, + header: "Company Name", + size: 200, + }, + { + accessorKey: "startDate", + id: "startDate", + enableSorting: true, + header: "Permit Start Date", + size: 140, + }, + { + accessorKey: "timeInQueue", + enableSorting: false, + id: "timeInQueue", + header: "Time in Queue (hh:mm)", + size: 200, + }, +]; diff --git a/frontend/src/features/queue/components/ApplicationInQueueReview.scss b/frontend/src/features/queue/components/ApplicationInQueueReview.scss new file mode 100644 index 000000000..1895c69e5 --- /dev/null +++ b/frontend/src/features/queue/components/ApplicationInQueueReview.scss @@ -0,0 +1,8 @@ +@use "../../../common/components/dashboard/Dashboard"; + +@include Dashboard.layout-box-style(".application-in-queue-review .breadcrumb"); + +.application-in-queue-review { + display: flex; + flex-direction: column; +} diff --git a/frontend/src/features/queue/components/ApplicationInQueueReview.tsx b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx new file mode 100644 index 000000000..075f8de46 --- /dev/null +++ b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx @@ -0,0 +1,152 @@ +import { useEffect, useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; + +import "./ApplicationInQueueReview.scss"; +import { QueueBreadcrumb } from "./QueueBreadcrumb"; +import { PERMIT_REVIEW_CONTEXTS } from "../../permits/types/PermitReviewContext"; +import { useCompanyInfoDetailsQuery } from "../../manageProfile/apiManager/hooks"; +import { usePowerUnitSubTypesQuery } from "../../manageVehicles/hooks/powerUnits"; +import { useTrailerSubTypesQuery } from "../../manageVehicles/hooks/trailers"; +import { calculateFeeByDuration } from "../../permits/helpers/feeSummary"; +import { PermitReview } from "../../permits/pages/Application/components/review/PermitReview"; +import { Application } from "../../permits/types/application"; +import { DEFAULT_PERMIT_TYPE } from "../../permits/types/PermitType"; +import { useFetchSpecialAuthorizations } from "../../settings/hooks/specialAuthorizations"; +import { Nullable } from "../../../common/types/common"; +import { getDefaultRequiredVal } from "../../../common/helpers/util"; +import { + useApproveApplicationInQueueMutation, + useRejectApplicationInQueueMutation, +} from "../hooks/hooks"; + +import { APPLICATION_STEPS, IDIR_ROUTES } from "../../../routes/constants"; + +export const ApplicationInQueueReview = ({ + applicationData, +}: { + applicationData?: Nullable; +}) => { + const companyId = getDefaultRequiredVal(0, applicationData?.companyId); + + const { data: specialAuth } = useFetchSpecialAuthorizations(companyId); + const isNoFeePermitType = Boolean(specialAuth?.noFeeType); + + const { data: companyInfo } = useCompanyInfoDetailsQuery(companyId); + const doingBusinessAs = companyInfo?.alternateName; + + const fee = isNoFeePermitType + ? "0" + : `${calculateFeeByDuration( + getDefaultRequiredVal(DEFAULT_PERMIT_TYPE, applicationData?.permitType), + getDefaultRequiredVal(0, applicationData?.permitData?.permitDuration), + )}`; + + const navigate = useNavigate(); + + const powerUnitSubTypesQuery = usePowerUnitSubTypesQuery(); + const trailerSubTypesQuery = useTrailerSubTypesQuery(); + const methods = useForm(); + + // For the confirmation checkboxes + // For Applications in Queue review, confirmation checkboxes are checked and disabled by default + const [allConfirmed, setAllConfirmed] = useState(true); + const [hasAttemptedSubmission, setHasAttemptedSubmission] = useState(false); + + const handleEdit = () => { + return; + }; + + const isSuccess = (status?: number) => status === 201; + + const { + mutateAsync: approveApplication, + data: approveApplicationResponse, + isPending: approveApplicationMutationPending, + } = useApproveApplicationInQueueMutation(); + + const handleApprove = async (): Promise => { + setHasAttemptedSubmission(true); + if (!allConfirmed) return; + + await approveApplication({ + applicationId: applicationData?.permitId, + companyId, + }); + }; + + useEffect(() => { + if (isSuccess(approveApplicationResponse?.status)) { + navigate(IDIR_ROUTES.STAFF_HOME); + } + }, [approveApplicationResponse, navigate]); + + const { + mutateAsync: rejectApplication, + data: rejectApplicationResponse, + isPending: rejectApplicationMutationPending, + } = useRejectApplicationInQueueMutation(); + + const handleReject = async (): Promise => { + setHasAttemptedSubmission(true); + if (!allConfirmed) return; + + await rejectApplication({ + applicationId: applicationData?.permitId, + companyId, + }); + }; + + useEffect(() => { + if (isSuccess(rejectApplicationResponse?.status)) { + navigate(IDIR_ROUTES.STAFF_HOME); + } + }, [rejectApplicationResponse, navigate]); + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + return ( +
+ + + + + +
+ ); +}; diff --git a/frontend/src/features/queue/components/ApplicationQueueLists.tsx b/frontend/src/features/queue/components/ApplicationQueueLists.tsx new file mode 100644 index 000000000..5e47235be --- /dev/null +++ b/frontend/src/features/queue/components/ApplicationQueueLists.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { usePermissionMatrix } from "../../../common/authentication/PermissionMatrix"; +import { TabLayout } from "../../../common/components/dashboard/TabLayout"; +import { ApplicationsInQueueList } from "./ApplicationsInQueueList"; +import { ClaimedApplicationsList } from "./ClaimedApplicationsList"; + +export const ApplicationQueueLists = React.memo(() => { + const tabs = []; + + const showApplicationsInQueueTab = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "STAFF_HOME_SCREEN", + permissionMatrixFunctionKey: "VIEW_QUEUE", + }, + }); + + if (showApplicationsInQueueTab) { + tabs.push({ + label: "Applications In Queue", + component: , + }); + } + + const showClaimedApplicationsTab = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "STAFF_HOME_SCREEN", + permissionMatrixFunctionKey: "VIEW_QUEUE", + }, + }); + + if (showClaimedApplicationsTab) { + tabs.push({ + label: "Claimed Applications", + component: , + }); + } + + return ; +}); + +ApplicationQueueLists.displayName = "ApplicationQueueLists"; diff --git a/frontend/src/features/queue/components/ApplicationsInQueueList.tsx b/frontend/src/features/queue/components/ApplicationsInQueueList.tsx new file mode 100644 index 000000000..598dd343a --- /dev/null +++ b/frontend/src/features/queue/components/ApplicationsInQueueList.tsx @@ -0,0 +1,186 @@ +import { RowSelectionState } from "@tanstack/table-core"; +import { + MaterialReactTable, + useMaterialReactTable, +} from "material-react-table"; +import { useCallback, useContext, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { SnackBarContext } from "../../../App"; +import OnRouteBCContext from "../../../common/authentication/OnRouteBCContext"; +import { NoRecordsFound } from "../../../common/components/table/NoRecordsFound"; +import { + defaultTableInitialStateOptions, + defaultTableOptions, + defaultTableStateOptions, +} from "../../../common/helpers/tableHelper"; +import { + getDefaultNullableVal, + getDefaultRequiredVal, +} from "../../../common/helpers/util"; +import { Loading } from "../../../common/pages/Loading"; +import { APPLICATION_QUEUE_ROUTES } from "../../../routes/constants"; +import { ApplicationListItem } from "../../permits/types/application"; +import { + useClaimApplicationInQueueMutation, + useUnclaimedApplicationsInQueueQuery, +} from "../hooks/hooks"; +import { getApplicationInQueueColumnDefinition } from "./ApplicationInQueueColumnDefinition"; + +export const ApplicationsInQueueList = () => { + const { + unclaimedApplicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + } = useUnclaimedApplicationsInQueueQuery(); + + const { + data: unclaimedApplications, + isError: unclaimedApplicationsError, + isPending: unclaimedApplicationsPending, + isFetching: unclaimedapplicationsFetching, + } = unclaimedApplicationsInQueueQuery; + + const [showTable, setShowTable] = useState(false); + + useEffect(() => { + const totalCount = getDefaultRequiredVal( + 0, + unclaimedApplications?.meta?.totalItems, + ); + setShowTable(totalCount > 0); + }, [unclaimedApplications?.meta?.totalItems]); + + const { idirUserDetails, userDetails } = useContext(OnRouteBCContext); + const userRole = getDefaultNullableVal( + idirUserDetails?.userRole, + userDetails?.userRole, + ); + + const snackBar = useContext(SnackBarContext); + + const [rowSelection, setRowSelection] = useState({}); + + useEffect(() => { + if (unclaimedApplicationsError) { + snackBar.setSnackBar({ + message: "An unexpected error occurred.", + showSnackbar: true, + setShowSnackbar: () => true, + alertType: "error", + }); + } + }, [unclaimedApplicationsError]); + + const [selectedApplication, setSelectedApplication] = + useState(); + + const handleFollowApplicationLink = (application: ApplicationListItem) => { + setSelectedApplication(application); + handleClaimApplication(application); + }; + + const { + mutateAsync: claimApplication, + data: claimApplicationResponse, + isPending: claimApplicationPending, + } = useClaimApplicationInQueueMutation(); + + const handleClaimApplication = async (application: ApplicationListItem) => { + await claimApplication({ + companyId: application.companyId, + applicationId: application.permitId, + }); + }; + + const navigate = useNavigate(); + + const isSuccess = (status?: number) => status === 201; + + useEffect(() => { + if (isSuccess(claimApplicationResponse?.status)) { + navigate( + APPLICATION_QUEUE_ROUTES.REVIEW( + selectedApplication?.companyId, + selectedApplication?.permitId, + ), + ); + } + }, [claimApplicationResponse]); + + const columns = getApplicationInQueueColumnDefinition( + handleFollowApplicationLink, + ); + + const table = useMaterialReactTable({ + ...defaultTableOptions, + columns, + data: getDefaultRequiredVal([], unclaimedApplications?.items), + initialState: { + ...defaultTableInitialStateOptions, + }, + state: { + ...defaultTableStateOptions, + showAlertBanner: unclaimedApplicationsError, + showProgressBars: unclaimedapplicationsFetching, + columnVisibility: { applicationId: true }, + isLoading: unclaimedApplicationsPending, + rowSelection, + pagination, + sorting, + }, + layoutMode: "grid", + displayColumnDefOptions: { + "mrt-row-select": { + size: 10, + }, + "mrt-row-actions": { + header: "", + size: 40, + }, + }, + enableRowActions: true, + enableRowSelection: false, + onRowSelectionChange: useCallback(setRowSelection, [userRole]), + getRowId: (originalRow) => { + const applicationRow = originalRow as ApplicationListItem; + return applicationRow.permitId; + }, + renderTopToolbar: false, + enableGlobalFilter: false, + autoResetPageIndex: false, + manualFiltering: true, + manualPagination: true, + manualSorting: true, + rowCount: getDefaultRequiredVal(0, unclaimedApplications?.meta?.totalItems), + pageCount: getDefaultRequiredVal(0, unclaimedApplications?.meta?.pageCount), + onSortingChange: setSorting, + onPaginationChange: setPagination, + enablePagination: true, + enableBottomToolbar: true, + muiToolbarAlertBannerProps: unclaimedApplicationsError + ? { + color: "error", + children: "Error loading data", + } + : undefined, + muiTableBodyRowProps: { + className: "applications-in-queue-list__row", + }, + }); + + if (claimApplicationPending) return ; + + return ( + <> + {showTable ? ( +
+ +
+ ) : ( + + )} + + ); +}; diff --git a/frontend/src/features/queue/components/ClaimedApplicationModal.scss b/frontend/src/features/queue/components/ClaimedApplicationModal.scss new file mode 100644 index 000000000..69d8d8992 --- /dev/null +++ b/frontend/src/features/queue/components/ClaimedApplicationModal.scss @@ -0,0 +1,74 @@ +@import "../../../themes/orbcStyles.scss"; + +.claimed-application-modal { + & &__container { + width: 100%; + display: flex; + flex-direction: column; + } + + &__header { + padding: 2rem 1.5rem; + display: flex; + flex-direction: row; + align-items: center; + background-color: $bc-background-light-grey; + } + + &__title { + font-weight: 600; + font-size: 1.5rem; + color: $bc-black; + } + + &__body { + padding: 1.5rem; + display: flex; + flex-direction: column; + align-items: flex-start; + } + + &__text { + padding-bottom: 1.5rem; + } + + &__footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + padding: 0 1.5rem 1.5rem 1.5rem; + + } + & &__button { + &--cancel { + margin-right: 1.5rem; + cursor: pointer; + background-color: $bc-background-light-grey; + color: $bc-black; + border: 2px solid $bc-background-light-grey; + box-shadow: none; + + &:hover, &:focus { + background-color: $bc-background-light-grey; + border: 2px solid $bc-border-grey; + box-shadow: none; + } + } + + &--confirm { + color: $white; + background-color: $bc-primary-blue; + font-weight: bold; + cursor: pointer; + + &:hover, &:focus { + background-color: $button-hover + } + + &:disabled { + background-color: $bc-red-disabled; + color: $white; + } + } + } +} diff --git a/frontend/src/features/queue/components/ClaimedApplicationModal.tsx b/frontend/src/features/queue/components/ClaimedApplicationModal.tsx new file mode 100644 index 000000000..4c2383e01 --- /dev/null +++ b/frontend/src/features/queue/components/ClaimedApplicationModal.tsx @@ -0,0 +1,78 @@ +import { Button, Dialog } from "@mui/material"; +import { FormProvider, useForm } from "react-hook-form"; +import "./ClaimedApplicationModal.scss"; + +export const ClaimedApplicationModal = ({ + showModal, + onCancel, + onConfirm, + currentClaimant, +}: { + showModal: boolean; + onCancel: () => void; + onConfirm: () => Promise; + currentClaimant: string; +}) => { + const formMethods = useForm<{ comment: string }>({ + defaultValues: { + comment: "", + }, + reValidateMode: "onChange", + }); + + const handleCancel = () => onCancel(); + const handleConfirm = () => onConfirm(); + + return ( + +
+ + Claimed Application + +
+ + +
+ + This application is already claimed by{" "} + {currentClaimant}. + + + All unsaved changes will be lost. Would you like to claim it + instead? + +
+ +
+ + + +
+
+
+ ); +}; diff --git a/frontend/src/features/queue/components/ClaimedApplicationsList.tsx b/frontend/src/features/queue/components/ClaimedApplicationsList.tsx new file mode 100644 index 000000000..4653da919 --- /dev/null +++ b/frontend/src/features/queue/components/ClaimedApplicationsList.tsx @@ -0,0 +1,209 @@ +import { RowSelectionState } from "@tanstack/table-core"; +import { + MaterialReactTable, + useMaterialReactTable, +} from "material-react-table"; +import { useCallback, useContext, useEffect, useState } from "react"; + +import { useNavigate } from "react-router-dom"; +import { SnackBarContext } from "../../../App"; +import OnRouteBCContext from "../../../common/authentication/OnRouteBCContext"; +import { NoRecordsFound } from "../../../common/components/table/NoRecordsFound"; +import { + defaultTableInitialStateOptions, + defaultTableOptions, + defaultTableStateOptions, +} from "../../../common/helpers/tableHelper"; +import { + getDefaultNullableVal, + getDefaultRequiredVal, +} from "../../../common/helpers/util"; +import { Loading } from "../../../common/pages/Loading"; +import { APPLICATION_QUEUE_ROUTES } from "../../../routes/constants"; +import { ApplicationListItem } from "../../permits/types/application"; +import { + useClaimApplicationInQueueMutation, + useClaimedApplicationsInQueueQuery, +} from "../hooks/hooks"; +import { ClaimedApplicationModal } from "./ClaimedApplicationModal"; +import { getUnclaimedApplicationInQueueColumnDefinition } from "./UnclaimedApplicationInQueueColumnDefinition"; + +export const ClaimedApplicationsList = () => { + const { + claimedApplicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + } = useClaimedApplicationsInQueueQuery(); + + const { + data: claimedApplications, + isError: claimedApplicationsError, + isPending: claimedApplicationsPending, + isFetching: claimedApplicationsFetching, + } = claimedApplicationsInQueueQuery; + + const [showTable, setShowTable] = useState(false); + + useEffect(() => { + const totalCount = getDefaultRequiredVal( + 0, + claimedApplications?.meta?.totalItems, + ); + setShowTable(totalCount > 0); + }, [claimedApplications?.meta?.totalItems]); + + const { idirUserDetails, userDetails } = useContext(OnRouteBCContext); + const userRole = getDefaultNullableVal( + idirUserDetails?.userRole, + userDetails?.userRole, + ); + + const snackBar = useContext(SnackBarContext); + + const [rowSelection, setRowSelection] = useState({}); + + useEffect(() => { + if (claimedApplicationsError) { + snackBar.setSnackBar({ + message: "An unexpected error occurred.", + showSnackbar: true, + setShowSnackbar: () => true, + alertType: "error", + }); + } + }, [claimedApplicationsError]); + + const [selectedApplication, setSelectedApplication] = + useState(); + + const [showClaimedApplicationModal, setShowClaimedApplicationModal] = + useState(false); + + const handleFollowApplicationLink = (application: ApplicationListItem) => { + setSelectedApplication(application); + + if (idirUserDetails?.userName === application.claimedBy) { + handleClaimApplication(application); + } else { + setShowClaimedApplicationModal(true); + } + }; + + const { + mutateAsync: claimApplication, + data: claimApplicationResponse, + isPending: claimApplicationPending, + } = useClaimApplicationInQueueMutation(); + + const handleClaimApplication = async (application: ApplicationListItem) => { + await claimApplication({ + companyId: application.companyId, + applicationId: application.permitId, + }); + }; + + const navigate = useNavigate(); + + const isSuccess = (status?: number) => status === 201; + + useEffect(() => { + if (isSuccess(claimApplicationResponse?.status)) { + navigate( + APPLICATION_QUEUE_ROUTES.REVIEW( + selectedApplication?.companyId, + selectedApplication?.permitId, + ), + ); + } + }, [claimApplicationResponse]); + + const columns = getUnclaimedApplicationInQueueColumnDefinition( + handleFollowApplicationLink, + ); + + const table = useMaterialReactTable({ + ...defaultTableOptions, + columns, + data: getDefaultRequiredVal([], claimedApplications?.items), + initialState: { + ...defaultTableInitialStateOptions, + }, + state: { + ...defaultTableStateOptions, + showAlertBanner: claimedApplicationsError, + showProgressBars: claimedApplicationsFetching, + columnVisibility: { applicationId: true }, + isLoading: claimedApplicationsPending, + rowSelection, + pagination, + sorting, + }, + layoutMode: "grid", + displayColumnDefOptions: { + "mrt-row-select": { + size: 10, + }, + "mrt-row-actions": { + header: "", + size: 40, + }, + }, + enableRowActions: true, + enableRowSelection: false, + onRowSelectionChange: useCallback(setRowSelection, [userRole]), + getRowId: (originalRow) => { + const applicationRow = originalRow as ApplicationListItem; + return applicationRow.permitId; + }, + renderTopToolbar: false, + enableGlobalFilter: false, + autoResetPageIndex: false, + manualFiltering: true, + manualPagination: true, + manualSorting: true, + rowCount: getDefaultRequiredVal(0, claimedApplications?.meta?.totalItems), + pageCount: getDefaultRequiredVal(0, claimedApplications?.meta?.pageCount), + onSortingChange: setSorting, + onPaginationChange: setPagination, + enablePagination: true, + enableBottomToolbar: true, + muiToolbarAlertBannerProps: claimedApplicationsError + ? { + color: "error", + children: "Error loading data", + } + : undefined, + muiTableBodyRowProps: { + className: "claimed-applications-list__row", + }, + }); + + if (claimApplicationPending) return ; + + return ( + <> + {showTable ? ( +
+
+ +
+ setShowClaimedApplicationModal(false)} + onConfirm={() => + handleClaimApplication(selectedApplication as ApplicationListItem) + } + currentClaimant={getDefaultRequiredVal( + "", + selectedApplication?.claimedBy, + )} + /> +
+ ) : ( + + )} + + ); +}; diff --git a/frontend/src/features/queue/components/QueueBreadcrumb.tsx b/frontend/src/features/queue/components/QueueBreadcrumb.tsx new file mode 100644 index 000000000..34d624810 --- /dev/null +++ b/frontend/src/features/queue/components/QueueBreadcrumb.tsx @@ -0,0 +1,42 @@ +import { useNavigate } from "react-router-dom"; +import { Nullable } from "../../../common/types/common"; +import { ApplicationStep, IDIR_ROUTES } from "../../../routes/constants"; +import { Breadcrumb } from "../../../common/components/breadcrumb/Breadcrumb"; + +export const QueueBreadcrumb = ({ + applicationNumber, + applicationStep, +}: { + applicationNumber?: Nullable; + applicationStep: ApplicationStep; +}) => { + const navigate = useNavigate(); + + const allLinks = [ + { + text: "Home", + onClick: () => navigate(IDIR_ROUTES.STAFF_HOME, { replace: true }), + }, + + { + text: `Application #: ${applicationNumber}`, + }, + ]; + + const getLinks = () => { + const filteredLinks = allLinks.filter( + (_, index) => index <= applicationStep, + ); + + return filteredLinks.map((link, index) => { + if (index === applicationStep) { + return { + text: link.text, + }; + } + return link; + }); + }; + + return ; +}; diff --git a/frontend/src/features/queue/components/UnclaimedApplicationInQueueColumnDefinition.tsx b/frontend/src/features/queue/components/UnclaimedApplicationInQueueColumnDefinition.tsx new file mode 100644 index 000000000..b2ad128f8 --- /dev/null +++ b/frontend/src/features/queue/components/UnclaimedApplicationInQueueColumnDefinition.tsx @@ -0,0 +1,86 @@ +import { Box, Tooltip } from "@mui/material"; +import { MRT_ColumnDef } from "material-react-table"; +import { CustomNavLink } from "../../../common/components/links/CustomNavLink"; +import { APPLICATION_QUEUE_ROUTES } from "../../../routes/constants"; +import { ApplicationListItem } from "../../permits/types/application"; +import { getPermitTypeName } from "../../permits/types/PermitType"; + +export const getUnclaimedApplicationInQueueColumnDefinition = ( + handleFollowApplicationLink: (application: ApplicationListItem) => void, +): MRT_ColumnDef[] => [ + { + accessorKey: "applicationNumber", + id: "applicationNumber", + enableSorting: false, + header: "Application #", + accessorFn: (row) => row.applicationNumber, + Cell: (props: { cell: any; row: any }) => { + const application = props.row.original; + const companyId = application.companyId; + const permitId = application.permitId; + return ( + { + e.preventDefault(); + handleFollowApplicationLink(application); + }} + to={APPLICATION_QUEUE_ROUTES.REVIEW(companyId, permitId)} + className="column-link column-link--application-details" + > + {props.cell.getValue()} + + ); + }, + size: 200, + }, + { + accessorKey: "permitType", + id: "permitType", + enableSorting: false, + header: "Permit Type", + Cell: (props: { cell: any }) => { + const permitTypeName = getPermitTypeName(props.cell.getValue()); + return ( + + {props.cell.getValue()} + + ); + }, + size: 80, + }, + { + accessorKey: "plate", + id: "plate", + enableSorting: false, + header: "Plate", + size: 50, + }, + { + accessorKey: "legalName", + id: "legalName", + enableSorting: false, + header: "Company Name", + size: 200, + }, + { + accessorKey: "startDate", + id: "startDate", + enableSorting: true, + header: "Permit Start Date", + size: 140, + }, + { + accessorKey: "timeInQueue", + enableSorting: false, + id: "timeInQueue", + header: "Time in Queue (hh:mm)", + size: 200, + }, + { + accessorKey: "claimedBy", + enableSorting: false, + id: "claimedBy", + header: "Claimed By", + size: 140, + }, +]; diff --git a/frontend/src/features/queue/helpers/canViewApplicationQueue.ts b/frontend/src/features/queue/helpers/canViewApplicationQueue.ts new file mode 100644 index 000000000..a7c057cbe --- /dev/null +++ b/frontend/src/features/queue/helpers/canViewApplicationQueue.ts @@ -0,0 +1,14 @@ +import { + IDIR_USER_ROLE, + IDIRUserRoleType, +} from "../../../common/authentication/types"; +import { Optional } from "../../../common/types/common"; + +const allowableRolesForApplicationQueue: IDIRUserRoleType[] = [ + IDIR_USER_ROLE.CTPO, + IDIR_USER_ROLE.PPC_CLERK, + IDIR_USER_ROLE.SYSTEM_ADMINISTRATOR, +]; + +export const canViewApplicationQueue = (userRole: Optional) => + allowableRolesForApplicationQueue.includes(userRole as IDIRUserRoleType); diff --git a/frontend/src/features/queue/hooks/hooks.ts b/frontend/src/features/queue/hooks/hooks.ts new file mode 100644 index 000000000..6d88eee4b --- /dev/null +++ b/frontend/src/features/queue/hooks/hooks.ts @@ -0,0 +1,244 @@ +import { useContext } from "react"; +import OnRouteBCContext from "../../../common/authentication/OnRouteBCContext"; +import { IDIRUserRoleType } from "../../../common/authentication/types"; +import { canViewApplicationQueue } from "../helpers/canViewApplicationQueue"; +import { MRT_PaginationState, MRT_SortingState } from "material-react-table"; +import { + keepPreviousData, + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; +import { + claimApplicationInQueue, + getApplicationsInQueue, + getClaimedApplicationsInQueue, + getUnclaimedApplicationsInQueue, + updateApplicationQueueStatus, +} from "../apiManager/queueAPI"; +import { Nullable } from "../../../common/types/common"; +import { SnackBarContext } from "../../../App"; +import { CASE_ACTIVITY_TYPES } from "../types/CaseActivityType"; +import { AxiosError } from "axios"; +import { useTableControls } from "../../permits/hooks/useTableControls"; + +const QUEUE_QUERY_KEYS_BASE = "queue"; + +const QUEUE_QUERY_KEYS = { + ALL: (pagination: MRT_PaginationState, sorting: MRT_SortingState) => [ + [QUEUE_QUERY_KEYS_BASE, pagination, sorting], + ], + CLAIMED: (pagination: MRT_PaginationState, sorting: MRT_SortingState) => [ + [QUEUE_QUERY_KEYS_BASE, pagination, sorting], + ], + UNCLAIMED: (pagination: MRT_PaginationState, sorting: MRT_SortingState) => [ + [QUEUE_QUERY_KEYS_BASE, pagination, sorting], + ], + DETAIL: (applicationNumber: string) => [ + QUEUE_QUERY_KEYS_BASE, + { applicationNumber }, + ], +}; + +/** + * Hook that fetches all applications in queue (PENDING_REVIEW, IN_REVIEW) for staff and manages its pagination state. + * This is the data that is consumed by the ApplicationsInReviewList component. + * @returns All applications in queue(PENDING_REVIEW, IN_REVIEW) along with pagination state and setter + */ +export const useApplicationsInQueueQuery = () => { + const { idirUserDetails, companyId } = useContext(OnRouteBCContext); + const userRole = idirUserDetails?.userRole as IDIRUserRoleType; + + // if typeof company === "undefined" here we know that the staff user is NOT acting as a company + const getStaffQueue = + canViewApplicationQueue(userRole) && typeof companyId === "undefined"; + + const { pagination, setPagination, sorting, setSorting, orderBy } = + useTableControls(); + + const applicationsInQueueQuery = useQuery({ + queryKey: QUEUE_QUERY_KEYS.ALL(pagination, sorting), + queryFn: () => + getApplicationsInQueue( + { + page: pagination.pageIndex, + take: pagination.pageSize, + orderBy, + }, + getStaffQueue, + companyId, + ), + refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground + refetchOnMount: "always", + placeholderData: keepPreviousData, + }); + + return { + applicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + }; +}; + +/** + * Hook that fetches all claimed applications in queue (IN_REVIEW) for staff and manages its pagination state. + * This is the data that is consumed by the ClaimedApplicationsList component + * @returns All claimed applications in queue (IN_REVIEW) along with pagination state and setter + */ +export const useClaimedApplicationsInQueueQuery = () => { + const { pagination, setPagination, sorting, setSorting, orderBy } = + useTableControls({ pageSize: 25 }); + + const claimedApplicationsInQueueQuery = useQuery({ + queryKey: QUEUE_QUERY_KEYS.CLAIMED(pagination, sorting), + queryFn: () => + getClaimedApplicationsInQueue({ + page: pagination.pageIndex, + take: pagination.pageSize, + orderBy, + }), + refetchInterval: 30000, + refetchOnWindowFocus: true, + refetchOnMount: "always", + placeholderData: keepPreviousData, + }); + + return { + claimedApplicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + }; +}; + +/** + * Hook that fetches all unclaimed applications in queue (PENDING_REVIEW) for staff and manages its pagination state. + * This is the data that is consumed by the ApplicationsInQueueList component + * @returns All unclaimed applications in queue (PENDING_REVIEW) along with pagination state and setter + */ +export const useUnclaimedApplicationsInQueueQuery = () => { + const { pagination, setPagination, sorting, setSorting, orderBy } = + useTableControls({ pageSize: 25 }); + + const unclaimedApplicationsInQueueQuery = useQuery({ + queryKey: QUEUE_QUERY_KEYS.UNCLAIMED(pagination, sorting), + queryFn: () => + getUnclaimedApplicationsInQueue({ + page: pagination.pageIndex, + take: pagination.pageSize, + orderBy, + }), + refetchInterval: 30000, + refetchOnWindowFocus: true, + refetchOnMount: "always", + placeholderData: keepPreviousData, + }); + + return { + unclaimedApplicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + }; +}; + +export const useClaimApplicationInQueueMutation = () => { + return useMutation({ + mutationFn: (data: { + companyId: Nullable; + applicationId: Nullable; + }) => { + const { companyId, applicationId } = data; + + return claimApplicationInQueue(companyId, applicationId); + }, + }); +}; + +export const useWithdrawApplicationInQueueMutation = () => { + const { invalidate } = useInvalidateApplicationsInQueue(); + const { setSnackBar } = useContext(SnackBarContext); + + return useMutation({ + mutationFn: (applicationId: string) => { + return updateApplicationQueueStatus({ + applicationId, + caseActivityType: CASE_ACTIVITY_TYPES.WITHDRAWN, + }); + }, + onSuccess: () => { + setSnackBar({ + showSnackbar: true, + setShowSnackbar: () => true, + message: "Withdrawn to Applications in Progress", + alertType: "info", + }); + invalidate(); + }, + onError: (err: AxiosError) => err, + }); +}; + +export const useApproveApplicationInQueueMutation = () => { + const { invalidate } = useInvalidateApplicationsInQueue(); + + return useMutation({ + mutationFn: ({ + applicationId, + companyId, + }: { + applicationId: Nullable; + companyId: number; + }) => { + return updateApplicationQueueStatus({ + applicationId, + caseActivityType: CASE_ACTIVITY_TYPES.APPROVED, + companyId, + }); + }, + onSuccess: () => { + invalidate(); + }, + onError: (err: AxiosError) => err, + }); +}; + +export const useRejectApplicationInQueueMutation = () => { + const { invalidate } = useInvalidateApplicationsInQueue(); + + return useMutation({ + mutationFn: ({ + applicationId, + companyId, + }: { + applicationId: Nullable; + companyId: number; + }) => { + return updateApplicationQueueStatus({ + applicationId, + caseActivityType: CASE_ACTIVITY_TYPES.REJECTED, + companyId, + }); + }, + onSuccess: () => { + invalidate(); + }, + onError: (err: AxiosError) => err, + }); +}; + +export const useInvalidateApplicationsInQueue = () => { + const queryClient = useQueryClient(); + + return { + invalidate: () => { + queryClient.invalidateQueries({ + queryKey: [QUEUE_QUERY_KEYS_BASE], + }); + }, + }; +}; diff --git a/frontend/src/features/queue/pages/ReviewApplicationInQueue.tsx b/frontend/src/features/queue/pages/ReviewApplicationInQueue.tsx new file mode 100644 index 000000000..ec18dc1d0 --- /dev/null +++ b/frontend/src/features/queue/pages/ReviewApplicationInQueue.tsx @@ -0,0 +1,61 @@ +import { Box } from "@mui/material"; +import { Navigate, useParams } from "react-router-dom"; +import { Banner } from "../../../common/components/dashboard/components/banner/Banner"; +import { Loading } from "../../../common/pages/Loading"; +import { useApplicationDetailsQuery } from "../../permits/hooks/hooks"; +import { ApplicationInQueueReview } from "../components/ApplicationInQueueReview"; +import { + applyWhenNotNullable, + getDefaultRequiredVal, +} from "../../../common/helpers/util"; +import { ERROR_ROUTES } from "../../../routes/constants"; +import { deserializeApplicationResponse } from "../../permits/helpers/deserializeApplication"; +import { UniversalUnexpected } from "../../../common/pages/UniversalUnexpected"; + +export const ReviewApplicationInQueue = () => { + const { companyId: companyIdParam, permitId: permitIdParam } = useParams(); + + const companyId: number = applyWhenNotNullable( + (id) => Number(id), + companyIdParam, + 0, + ); + const permitId = getDefaultRequiredVal("", permitIdParam); + + const { + query: { data: applicationData, isLoading: applicationDataIsLoading }, + } = useApplicationDetailsQuery({ + companyId, + permitId, + }); + + if (!companyId || !permitId) { + return ; + } + + if (applicationDataIsLoading) { + return ; + } + + if (!applicationData) { + return ; + } + + return ( +
+ + + + + +
+ ); +}; diff --git a/frontend/src/features/permits/types/ApplicationQueueStatus.ts b/frontend/src/features/queue/types/ApplicationQueueStatus.ts similarity index 100% rename from frontend/src/features/permits/types/ApplicationQueueStatus.ts rename to frontend/src/features/queue/types/ApplicationQueueStatus.ts diff --git a/frontend/src/features/permits/types/CaseActivityType.ts b/frontend/src/features/queue/types/CaseActivityType.ts similarity index 100% rename from frontend/src/features/permits/types/CaseActivityType.ts rename to frontend/src/features/queue/types/CaseActivityType.ts diff --git a/frontend/src/features/settings/components/creditAccount/CloseCreditAccountModal.scss b/frontend/src/features/settings/components/creditAccount/CloseCreditAccountModal.scss index 8ba92e8e5..a10f2f3e6 100644 --- a/frontend/src/features/settings/components/creditAccount/CloseCreditAccountModal.scss +++ b/frontend/src/features/settings/components/creditAccount/CloseCreditAccountModal.scss @@ -92,7 +92,7 @@ } &:disabled { - background-color: #EBC0C1; + background-color: $bc-red-disabled; color: $white; } } diff --git a/frontend/src/features/settings/components/creditAccount/RemoveUsersModal.scss b/frontend/src/features/settings/components/creditAccount/RemoveUsersModal.scss index 7cd27618b..a7b0bd45d 100644 --- a/frontend/src/features/settings/components/creditAccount/RemoveUsersModal.scss +++ b/frontend/src/features/settings/components/creditAccount/RemoveUsersModal.scss @@ -73,7 +73,7 @@ } &:disabled { - background-color: #EBC0C1; + background-color: $bc-red-disabled; color: $white; } } diff --git a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/vehicles/LOADesignateVehicles.tsx b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/vehicles/LOADesignateVehicles.tsx index 12b4b4787..9e4cccf3c 100644 --- a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/vehicles/LOADesignateVehicles.tsx +++ b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/vehicles/LOADesignateVehicles.tsx @@ -74,11 +74,8 @@ const getVehicleDetailsForSelected = ( }; export const LOADesignateVehicles = () => { - const { companyId } = useContext(OnRouteBCContext); - const companyIdStr = applyWhenNotNullable( - id => `${id}`, - companyId, - ); + const { companyId: companyIdFromContext } = useContext(OnRouteBCContext); + const companyId = getDefaultRequiredVal(0, companyIdFromContext); const [vehicleTab, setVehicleTab] = useState(LOA_VEHICLE_TABS.POWER_UNITS); @@ -94,8 +91,8 @@ export const LOADesignateVehicles = () => { const selectedPowerUnits = watch("selectedVehicles.powerUnits"); const selectedTrailers = watch("selectedVehicles.trailers"); - const powerUnitsQuery = usePowerUnitsQuery(companyIdStr); - const trailersQuery = useTrailersQuery(companyIdStr); + const powerUnitsQuery = usePowerUnitsQuery(companyId); + const trailersQuery = useTrailersQuery(companyId); const powerUnitSubTypesQuery = usePowerUnitSubTypesQuery(); const trailerSubTypesQuery = useTrailerSubTypesQuery(); const powerUnitSubTypes = useMemo(() => getDefaultRequiredVal( diff --git a/frontend/src/routes/Routes.tsx b/frontend/src/routes/Routes.tsx index a1eca01eb..db22cf76e 100644 --- a/frontend/src/routes/Routes.tsx +++ b/frontend/src/routes/Routes.tsx @@ -33,6 +33,8 @@ import { IssuanceErrorPage } from "../common/pages/IssuanceErrorPage"; import IDPRedirect from "../common/components/idpredirect/IDPRedirect"; import { ShoppingCartDashboard } from "../features/permits/ShoppingCartDashboard"; import { DocumentUnavailable } from "../common/pages/DocumentUnavailable"; +import { StaffDashboard } from "../features/idir/StaffDashboard"; +import { ReviewApplicationInQueue } from "../features/queue/pages/ReviewApplicationInQueue"; export const AppRoutes = () => { return ( @@ -61,7 +63,7 @@ export const AppRoutes = () => { element={} /> } /> - + {/* Wizard Routes */} {/* Wizard Routes only require that a user @@ -111,6 +113,34 @@ export const AppRoutes = () => { /> + + } + > + } + /> + + } + /> + { + // TODO: placeholder route for edit step + /* } + /> */ + } + + { } > } /> @@ -138,7 +168,7 @@ export const AppRoutes = () => { } > } /> diff --git a/frontend/src/routes/constants.ts b/frontend/src/routes/constants.ts index facb0a55b..08e9dec68 100644 --- a/frontend/src/routes/constants.ts +++ b/frontend/src/routes/constants.ts @@ -5,6 +5,7 @@ import { PermitType } from "../features/permits/types/PermitType"; export const ROUTE_PLACEHOLDERS = { PERMIT_ID: "permitId", PERMIT_TYPE: "permitType", + COMPANY_ID: "companyId", }; /** @@ -18,7 +19,7 @@ export const ROUTE_PLACEHOLDERS = { const DYNAMIC_ROUTE_URI = ( prefixUri: string, placeholderName: string, - value?: Nullable, + value?: Nullable, ) => { if (!value) { return `${prefixUri}/:${placeholderName}`; @@ -70,14 +71,28 @@ const PERMITS_ROUTE_BASE = "/permits"; export const PERMITS_ROUTES = { BASE: PERMITS_ROUTE_BASE, SUCCESS: `${PERMITS_ROUTE_BASE}/success`, - VOID: (companyId?: string, permitId?: string) => - `/companies/${companyId}${DYNAMIC_ROUTE_URI( + VOID: ( + companyId?: Nullable, + permitId?: Nullable, + ) => + `${DYNAMIC_ROUTE_URI( + "/companies", + ROUTE_PLACEHOLDERS.COMPANY_ID, + companyId, + )}${DYNAMIC_ROUTE_URI( PERMITS_ROUTE_BASE, ROUTE_PLACEHOLDERS.PERMIT_ID, permitId, )}/void`, - AMEND: (companyId?: string, permitId?: string) => - `/companies/${companyId}${DYNAMIC_ROUTE_URI( + AMEND: ( + companyId?: Nullable, + permitId?: Nullable, + ) => + `${DYNAMIC_ROUTE_URI( + "/companies", + ROUTE_PLACEHOLDERS.COMPANY_ID, + companyId, + )}${DYNAMIC_ROUTE_URI( PERMITS_ROUTE_BASE, ROUTE_PLACEHOLDERS.PERMIT_ID, permitId, @@ -120,6 +135,38 @@ export const APPLICATIONS_ROUTES = { )}/review`, }; +// Queue +const APPLICATION_QUEUE_ROUTE_BASE = "/queue"; +export const APPLICATION_QUEUE_ROUTES = { + BASE: APPLICATION_QUEUE_ROUTE_BASE, + REVIEW: ( + companyId?: Nullable, + permitId?: Nullable, + ) => + `${DYNAMIC_ROUTE_URI( + "/companies", + ROUTE_PLACEHOLDERS.COMPANY_ID, + companyId, + )}${DYNAMIC_ROUTE_URI( + APPLICATION_QUEUE_ROUTE_BASE, + ROUTE_PLACEHOLDERS.PERMIT_ID, + permitId, + )}/review`, + EDIT: ( + companyId?: Nullable, + permitId?: Nullable, + ) => + `${DYNAMIC_ROUTE_URI( + "/companies", + ROUTE_PLACEHOLDERS.COMPANY_ID, + companyId, + )}${DYNAMIC_ROUTE_URI( + APPLICATION_QUEUE_ROUTE_BASE, + ROUTE_PLACEHOLDERS.PERMIT_ID, + permitId, + )}/edit`, +}; + // Shopping Cart export const SHOPPING_CART_ROUTE_BASE = "/cart"; export const SHOPPING_CART_ROUTES = { @@ -143,6 +190,7 @@ export const IDIR_ROUTES = { SEARCH_RESULTS: `${IDIR_ROUTE_BASE}/search-results`, REPORTS: `${IDIR_ROUTE_BASE}/reports`, CREATE_COMPANY: `${IDIR_ROUTE_BASE}/create-company`, + STAFF_HOME: `${IDIR_ROUTE_BASE}/home`, }; // Payment diff --git a/frontend/src/themes/orbcStyles.scss b/frontend/src/themes/orbcStyles.scss index 80aea1080..128161cdc 100644 --- a/frontend/src/themes/orbcStyles.scss +++ b/frontend/src/themes/orbcStyles.scss @@ -28,6 +28,7 @@ $bc-messages-red-text: #a12722; $bc-text-links-blue: #1a5a96; $bc-primary-blue: #003366; $bc-red: #d8292f; +$bc-red-disabled: #ebc0c1; $bc-text-box-border-grey: #b2b5b6; $bc-white: #ffffff; $black: #000000; From 68d4471cad3f014f0009fa3e459f226edc84263e Mon Sep 17 00:00:00 2001 From: gchauhan-aot <113390759+gchauhan-aot@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:58:13 -0600 Subject: [PATCH 38/58] Resolving Linting Issues For Scheduler (#1633) --- .../src/modules/cgi-sftp/cgi-sftp.service.ts | 35 +++++++++++-------- .../src/modules/permit/permit.service.ts | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/scheduler/src/modules/cgi-sftp/cgi-sftp.service.ts b/scheduler/src/modules/cgi-sftp/cgi-sftp.service.ts index c25d8b1a2..dd8b5ffd5 100644 --- a/scheduler/src/modules/cgi-sftp/cgi-sftp.service.ts +++ b/scheduler/src/modules/cgi-sftp/cgi-sftp.service.ts @@ -10,24 +10,29 @@ import * as Client from 'ssh2-sftp-client'; export class CgiSftpService { private readonly logger = new Logger(CgiSftpService.name); - upload(fileData: Express.Multer.File, fileName: string) { + async upload(fileData: Express.Multer.File, fileName: string) { const sftp = new Client(); const connectionInfo: Client.ConnectOptions = getSFTPConnectionInfo(); const remotePath = process.env.CFS_REMOTE_PATH; //Remote CFS Path - sftp - .connect(connectionInfo) - .then(() => { - this.logger.log(`writing file ${remotePath}${fileName}`); - return sftp.put(fileData.buffer, remotePath + fileName); - }) - .catch((err) => { - this.logger.error(err); - throw new InternalServerErrorException(err); - }) - .finally(() => { - this.logger.log('closing connection'); - void sftp.end(); - }); + try { + await sftp.connect(connectionInfo); + this.logger.log(`Successfully connected to ${process.env.CFS_SFTP_HOST} via SFTP.`); + } catch (error) { + this.logger.error('Cannot connect to sftp.'); + this.logger.error(error); + } + try { + const res = await sftp.put(fileData.buffer, remotePath + fileName); + this.logger.log(`Successfully sent file ${fileName} via SFTP.`); + return res; + } catch (error) { + this.logger.error('Failed to send file via SFTP.'); + this.logger.error(error); + throw new InternalServerErrorException('Failed to send file via SFTP.'); + } finally { + this.logger.log('closing connection'); + void sftp.end(); + } } } diff --git a/scheduler/src/modules/permit/permit.service.ts b/scheduler/src/modules/permit/permit.service.ts index 89dfb5283..06c45107e 100644 --- a/scheduler/src/modules/permit/permit.service.ts +++ b/scheduler/src/modules/permit/permit.service.ts @@ -156,7 +156,7 @@ export class PermitService { await lastValueFrom( this.httpService .post(url, body, reqConfig) - .pipe(map((response) => response.data)), + .pipe(map((response) => response.data as JSON)), ); } catch (error) { this.logger.error(`Error in calling ${url}: ${error}`); From 0d6a1c3ca2ae808129061101a719a7b2763705f7 Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:28:37 -0700 Subject: [PATCH 39/58] ORV2-2578 - Restore edit button for permitReview component (#1634) Co-authored-by: praju-aot Co-authored-by: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Co-authored-by: zgong-gov --- .../components/review/PermitReview.tsx | 1 + .../components/review/ReviewActions.tsx | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx index 774fcca6e..c4c9fbfc8 100644 --- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx @@ -127,6 +127,7 @@ export const PermitReview = (props: PermitReviewProps) => { rejectApplicationMutationPending={ props.rejectApplicationMutationPending } + reviewContext={props.reviewContext} /> diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx index 3f705003f..203e2f137 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx @@ -1,8 +1,11 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { Box, Button } from "@mui/material"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPencil } from "@fortawesome/free-solid-svg-icons"; import "./ReviewActions.scss"; +import { + PERMIT_REVIEW_CONTEXTS, + PermitReviewContext, +} from "../../../../types/PermitReviewContext"; export const ReviewActions = ({ onEdit, @@ -14,6 +17,7 @@ export const ReviewActions = ({ approveApplicationMutationPending, onReject, rejectApplicationMutationPending, + reviewContext, }: { onEdit: () => void; continueBtnText?: string; @@ -24,24 +28,29 @@ export const ReviewActions = ({ approveApplicationMutationPending?: boolean; onReject?: () => Promise; rejectApplicationMutationPending?: boolean; + reviewContext: PermitReviewContext; }) => { return ( - {/* TODO restore Edit button once edit application step is complete - */} + { + // hide edit button until edit application in queue feature is complete + reviewContext !== PERMIT_REVIEW_CONTEXTS.QUEUE && ( + + ) + } {hasToCartButton ? (
+ } + /> + + +
+
+ ); +}; diff --git a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx index c9b3acba4..1e0667ece 100644 --- a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx @@ -25,6 +25,7 @@ import { SelectVehicleDropdown } from "./customFields/SelectVehicleDropdown"; import { BANNER_MESSAGES } from "../../../../../../../common/constants/bannerMessages"; import { PermitVehicleDetails } from "../../../../../types/PermitVehicleDetails"; import { EMPTY_VEHICLE_SUBTYPE } from "../../../../../../manageVehicles/helpers/vehicleSubtypes"; +import { LOADetail } from "../../../../../../settings/types/SpecialAuthorization"; import { PowerUnit, Trailer, @@ -82,6 +83,8 @@ const getEligibleSubtypeOptions = ( trailerSubtypes: VehicleSubType[], ineligiblePowerUnitSubtypes: VehicleSubType[], ineligibleTrailerSubtypes: VehicleSubType[], + allowedLOAPowerUnitSubtypes: string[], + allowedLOATrailerSubtypes: string[], vehicleType?: string, ) => { if ( @@ -102,6 +105,8 @@ const getEligibleSubtypeOptions = ( vehicleType, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, + allowedLOAPowerUnitSubtypes, + allowedLOATrailerSubtypes, ); }; @@ -113,6 +118,7 @@ export const VehicleDetails = ({ trailerSubtypes, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, + selectedLOAs, onSetSaveVehicle, onSetVehicle, onClearVehicle, @@ -124,6 +130,7 @@ export const VehicleDetails = ({ trailerSubtypes: VehicleSubType[]; ineligiblePowerUnitSubtypes: VehicleSubType[]; ineligibleTrailerSubtypes: VehicleSubType[]; + selectedLOAs: LOADetail[]; onSetSaveVehicle: (saveVehicle: boolean) => void; onSetVehicle: (vehicleDetails: PermitVehicleDetails) => void; onClearVehicle: (saveVehicle: boolean) => void; @@ -166,6 +173,50 @@ export const VehicleDetails = ({ EMPTY_VEHICLE_SUBTYPE, ]); + // Find vehicle subtypes that are allowed by LOAs + const permittedLOAPowerUnitIds = new Set([ + ...selectedLOAs.map(loa => loa.powerUnits) + .reduce((prevPowerUnits, currPowerUnits) => [ + ...prevPowerUnits, + ...currPowerUnits, + ], []), + ]); + + const permittedLOATrailerIds = new Set([ + ...selectedLOAs.map(loa => loa.trailers) + .reduce((prevTrailers, currTrailers) => [ + ...prevTrailers, + ...currTrailers, + ], []), + ]); + + const powerUnitsInInventory = vehicleOptions + .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.POWER_UNIT) as PowerUnit[]; + + const trailersInInventory = vehicleOptions + .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.TRAILER) as Trailer[]; + + const permittedLOAPowerUnitSubtypes = powerUnitsInInventory + .filter(powerUnit => permittedLOAPowerUnitIds.has(powerUnit.powerUnitId as string)) + .map(powerUnit => powerUnit.powerUnitTypeCode); + + const permittedLOATrailerSubtypes = trailersInInventory + .filter(trailer => permittedLOATrailerIds.has(trailer.trailerId as string)) + .map(trailer => trailer.trailerTypeCode); + + // Check if selected vehicle is an LOA vehicle + const isSelectedVehicleAllowedByLOA = Boolean(vehicleFormData.vehicleId) + && ( + permittedLOAPowerUnitIds.has(vehicleFormData.vehicleId as string) + || permittedLOATrailerIds.has(vehicleFormData.vehicleId as string) + ) + && ( + powerUnitsInInventory.map(powerUnit => powerUnit.powerUnitId) + .includes(vehicleFormData.vehicleId as string) + || trailersInInventory.map(trailer => trailer.trailerId) + .includes(vehicleFormData.vehicleId as string) + ); + useEffect(() => { // Update subtype options when vehicle type changes const subtypes = getEligibleSubtypeOptions( @@ -173,6 +224,8 @@ export const VehicleDetails = ({ trailerSubtypes, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, + permittedLOAPowerUnitSubtypes, + permittedLOATrailerSubtypes, vehicleType, ); setSubtypeOptions(subtypes); @@ -182,6 +235,8 @@ export const VehicleDetails = ({ ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, vehicleType, + permittedLOAPowerUnitSubtypes, + permittedLOATrailerSubtypes, ]); // Set the "Save to Inventory" radio button to false on render @@ -257,6 +312,13 @@ export const VehicleDetails = ({ } }; + // If the selected vehicle is an LOA vehicle, it should not be edited/saved to inventory + useEffect(() => { + if (isSelectedVehicleAllowedByLOA) { + setSaveVehicle(false); + } + }, [isSelectedVehicleAllowedByLOA]); + return ( @@ -305,8 +367,9 @@ export const VehicleDetails = ({ vehicleOptions={vehicleOptions} handleClearVehicle={() => onClearVehicle(saveVehicle)} handleSelectVehicle={onSelectVehicle} - ineligiblePowerUnitSubtypes={ineligiblePowerUnitSubtypes} - ineligibleTrailerSubtypes={ineligibleTrailerSubtypes} + ineligiblePowerUnitSubtypes={ineligiblePowerUnitSubtypes.map(({ typeCode }) => typeCode)} + ineligibleTrailerSubtypes={ineligibleTrailerSubtypes.map(({ typeCode }) => typeCode)} + loas={selectedLOAs} /> @@ -324,6 +387,8 @@ export const VehicleDetails = ({ width: formFieldStyle.width, customHelperText: "last 6 digits", }} + readOnly={isSelectedVehicleAllowedByLOA} + disabled={isSelectedVehicleAllowedByLOA} /> ))} + readOnly={isSelectedVehicleAllowedByLOA} + disabled={isSelectedVehicleAllowedByLOA} /> @@ -459,6 +534,8 @@ export const VehicleDetails = ({ "data-testid": "save-vehicle-yes", } as CustomInputHTMLAttributes } + readOnly={isSelectedVehicleAllowedByLOA} + disabled={isSelectedVehicleAllowedByLOA} /> } label="Yes" @@ -473,6 +550,8 @@ export const VehicleDetails = ({ "data-testid": "save-vehicle-no", } as CustomInputHTMLAttributes } + readOnly={isSelectedVehicleAllowedByLOA} + disabled={isSelectedVehicleAllowedByLOA} /> } label="No" diff --git a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx index 06f7b96cc..4505eefc4 100644 --- a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx @@ -17,13 +17,12 @@ import { VEHICLE_CHOOSE_FROM } from "../../../../../../constants/constants"; import { EMPTY_VEHICLE_UNIT_NUMBER } from "../../../../../../../../common/constants/constants"; import { Nullable } from "../../../../../../../../common/types/common"; import { PermitVehicleDetails } from "../../../../../../types/PermitVehicleDetails"; - +import { LOADetail } from "../../../../../../../settings/types/SpecialAuthorization"; import { PowerUnit, Trailer, VEHICLE_TYPES, Vehicle, - VehicleSubType, } from "../../../../../../../manageVehicles/types/Vehicle"; const GroupHeader = styled("div")(({ theme }) => ({ @@ -53,6 +52,7 @@ export const SelectVehicleDropdown = ({ handleClearVehicle, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, + loas, }: { chooseFrom: string; selectedVehicle: Nullable; @@ -60,8 +60,9 @@ export const SelectVehicleDropdown = ({ vehicleOptions: Vehicle[]; handleSelectVehicle: (vehicle: Vehicle) => void; handleClearVehicle: () => void; - ineligiblePowerUnitSubtypes: VehicleSubType[]; - ineligibleTrailerSubtypes: VehicleSubType[]; + ineligiblePowerUnitSubtypes: string[]; + ineligibleTrailerSubtypes: string[]; + loas: LOADetail[]; }) => { const sortedVehicles = sortVehicles(chooseFrom, vehicleOptions); @@ -69,6 +70,7 @@ export const SelectVehicleDropdown = ({ sortedVehicles, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, + loas, ); const selectedOption = selectedVehicle diff --git a/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx b/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx index 700da08c0..6abb845f1 100644 --- a/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx @@ -61,6 +61,7 @@ const TestFormWrapper = (props: React.PropsWithChildren) => { permitDuration: defaultDuration, expiryDate: getExpiryDate(currentDt, defaultDuration), commodities: [], + loas: [], }, }, reValidateMode: "onBlur", diff --git a/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss b/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss index 575c02b8c..b33423f60 100644 --- a/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss +++ b/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss @@ -1,7 +1,7 @@ @import "../../../../../../themes/orbcStyles"; .cv-pay-in-person-info { - padding: 1.5rem 0; + padding: 0; &__heading { color: $bc-black; diff --git a/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx b/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx index d624a74ff..b5587df1d 100644 --- a/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx +++ b/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx @@ -1,17 +1,21 @@ import { Controller, useFormContext } from "react-hook-form"; import { Box, RadioGroup, Typography } from "@mui/material"; + +import "./ChoosePaymentMethod.scss"; +import { CVPayInPersonInfo } from "./CVPayInPersonInfo"; import { PaymentOption } from "./PaymentOption"; import { PaymentMethodTypeCode } from "../../../../../../common/types/paymentMethods"; import { DEFAULT_EMPTY_CARD_TYPE, DEFAULT_EMPTY_PAYMENT_TYPE, } from "./types/PaymentMethodData"; -import "./ChoosePaymentMethod.scss"; export const ChoosePaymentMethod = ({ availablePaymentMethods, + showPayInPersonInfo, }: { availablePaymentMethods: PaymentMethodTypeCode[]; + showPayInPersonInfo: boolean; }) => { const { control, watch, setValue, clearErrors } = useFormContext(); const currPaymentMethod = watch("paymentMethod"); @@ -67,6 +71,10 @@ export const ChoosePaymentMethod = ({ handlePaymentMethodChange={handlePaymentMethodChange} /> ))} + + {showPayInPersonInfo ? ( + + ) : null} )} /> diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx index c4c9fbfc8..498be851a 100644 --- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx @@ -17,6 +17,8 @@ import { PermitContactDetails } from "../../../../types/PermitContactDetails"; import { PermitVehicleDetails } from "../../../../types/PermitVehicleDetails"; import { Application } from "../../../../types/application"; import { PermitCondition } from "../../../../types/PermitCondition"; +import { ReviewPermitLOAs } from "./ReviewPermitLOAs"; +import { LOADetail } from "../../../../../settings/types/SpecialAuthorization"; import { PERMIT_REVIEW_CONTEXTS, PermitReviewContext, @@ -56,6 +58,7 @@ interface PermitReviewProps { oldFields?: Nullable>; calculatedFee: string; doingBusinessAs?: Nullable; + loas?: Nullable; } export const PermitReview = (props: PermitReviewProps) => { @@ -83,6 +86,10 @@ export const PermitReview = (props: PermitReviewProps) => { oldFields={props.oldFields?.permitData?.contactDetails} /> + + - + Description - + + Conditions + {reviewConditions.map((row: PermitCondition) => { return ( @@ -42,7 +48,11 @@ export const ReviewConditionsTable = ({ key={row.condition} data-testid="review-permit-condition" > - + - + ; +}) => { + return loas && loas.length > 0 ? ( + + + + Letter of Authorization (LOA) + + + + + + + Selected LOA(s) + + + ({ + loa, + checked: true, + disabled: true, + }))} + /> + + + + ) : null; +}; diff --git a/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx b/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx index b3b5b3f76..63f36255d 100644 --- a/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx +++ b/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx @@ -49,6 +49,7 @@ export const defaultApplicationData = { startDate: getStartOfDate(toLocalDayjs(permitData.startDate)), expiryDate: getEndOfDate(toLocalDayjs(permitData.expiryDate)), }, + permitStatus: PERMIT_STATUSES.IN_PROGRESS, } as Application; export const companyInfo = getDefaultCompanyInfo(); diff --git a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss index 519f18cd9..b0d208663 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss +++ b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss @@ -20,7 +20,7 @@ & &__info { border-bottom: 1px solid $bc-border-grey; margin-bottom: 1.5rem; - padding: 1.5rem 0; + padding: 0 0 1.5rem 0; .info { &__body { diff --git a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx index 229e2ee1d..2cc8aac35 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx +++ b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx @@ -21,7 +21,6 @@ import { EditCartItemDialog } from "../../components/cart/EditCartItemDialog"; import { UpdateCartDialog } from "../../components/cart/UpdateCartDialog"; import { BCeID_USER_ROLE } from "../../../../common/authentication/types"; import { Loading } from "../../../../common/pages/Loading"; -import { CVPayInPersonInfo } from "../Application/components/pay/CVPayInPersonInfo"; import { PAYMENT_METHOD_TYPE_CODE, PaymentCardTypeCode, @@ -391,6 +390,7 @@ export const ShoppingCartPage = () => { {PPC_EMAIL}

+ { - - {!isStaffActingAsCompany && } + {!isFeeZero ? ( + + ) : null} {paymentFailed ? : null} diff --git a/frontend/src/features/permits/types/PermitData.ts b/frontend/src/features/permits/types/PermitData.ts index 381d12e89..c33774c62 100644 --- a/frontend/src/features/permits/types/PermitData.ts +++ b/frontend/src/features/permits/types/PermitData.ts @@ -4,6 +4,7 @@ import { Nullable } from "../../../common/types/common"; import { PermitContactDetails } from "./PermitContactDetails"; import { PermitVehicleDetails } from "./PermitVehicleDetails"; import { PermitMailingAddress } from "./PermitMailingAddress"; +import { LOADetail } from "../../settings/types/SpecialAuthorization"; import { PermitCondition } from "./PermitCondition"; export interface PermitData { @@ -18,4 +19,5 @@ export interface PermitData { companyName?: Nullable; doingBusinessAs?: Nullable; clientNumber?: Nullable; + loas?: Nullable; } diff --git a/frontend/src/features/permits/types/PermitType.ts b/frontend/src/features/permits/types/PermitType.ts index df4fc1046..1d2bb9341 100644 --- a/frontend/src/features/permits/types/PermitType.ts +++ b/frontend/src/features/permits/types/PermitType.ts @@ -116,3 +116,13 @@ export const permitTypeDisplayText = (permitType?: Nullable) => { export const isPermitTypeValid = (permitType?: Nullable) => { return permitType && (Object.values(PERMIT_TYPES) as string[]).includes(permitType.toUpperCase()); }; + +/** + * Determine whether or not a permit type is considered a term permit. + * @param permitType Type of permit + * @returns Whether or not the permit of that type is considered a term permit + */ +export const isTermPermitType = (permitType: PermitType) => { + return permitType === PERMIT_TYPES.TROS + || permitType === PERMIT_TYPES.TROW; +}; diff --git a/frontend/src/features/settings/apiManager/endpoints/endpoints.ts b/frontend/src/features/settings/apiManager/endpoints/endpoints.ts index 5da008e7c..509fafb76 100644 --- a/frontend/src/features/settings/apiManager/endpoints/endpoints.ts +++ b/frontend/src/features/settings/apiManager/endpoints/endpoints.ts @@ -20,18 +20,18 @@ export const SPECIAL_AUTH_API_ROUTES = { }, LOA: { ALL: (companyId: number | string, expired: boolean) => - `${SPECIAL_AUTH_API_BASE}/${companyId}/loas${expired ? "?expired=true" : ""}`, - DETAIL: (companyId: number | string, loaId: string) => + `${SPECIAL_AUTH_API_BASE}/${companyId}/loas?expired=${expired}`, + DETAIL: (companyId: number | string, loaId: number) => `${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}`, CREATE: (companyId: number | string) => `${SPECIAL_AUTH_API_BASE}/${companyId}/loas`, - UPDATE: (companyId: number | string, loaId: string) => + UPDATE: (companyId: number | string, loaId: number) => `${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}`, - REMOVE: (companyId: number | string, loaId: string) => + REMOVE: (companyId: number | string, loaId: number) => `${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}`, - DOWNLOAD: (companyId: number | string, loaId: string) => + DOWNLOAD: (companyId: number | string, loaId: number) => `${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}/documents?download=proxy`, - REMOVE_DOCUMENT: (companyId: number | string, loaId: string) => + REMOVE_DOCUMENT: (companyId: number | string, loaId: number) => `${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}/documents`, }, }; diff --git a/frontend/src/features/settings/apiManager/specialAuthorization.ts b/frontend/src/features/settings/apiManager/specialAuthorization.ts index 689054947..7448be6ca 100644 --- a/frontend/src/features/settings/apiManager/specialAuthorization.ts +++ b/frontend/src/features/settings/apiManager/specialAuthorization.ts @@ -4,6 +4,7 @@ import { LOADetail, NoFeePermitType, SpecialAuthorizationData } from "../types/S import { LOAFormData, serializeLOAFormData } from "../types/LOAFormData"; import { SPECIAL_AUTH_API_ROUTES } from "./endpoints/endpoints"; import { streamDownloadFile } from "../../../common/helpers/util"; +import { RequiredOrNull } from "../../../common/types/common"; import { httpDELETERequest, httpGETRequest, @@ -12,7 +13,6 @@ import { httpPUTRequest, httpPUTRequestWithFile, } from "../../../common/apiManager/httpRequestHandler"; -import { RequiredOrNull } from "../../../common/types/common"; /** * Get the LOAs for a given company. @@ -38,7 +38,7 @@ export const getLOAs = async ( */ export const getLOADetail = async ( companyId: number | string, - loaId: string, + loaId: number, ): Promise => { const response = await httpGETRequest( SPECIAL_AUTH_API_ROUTES.LOA.DETAIL(companyId, loaId), @@ -72,7 +72,7 @@ export const createLOA = async ( export const updateLOA = async ( LOAData: { companyId: number | string; - loaId: string; + loaId: number; data: LOAFormData; }, ): Promise> => { @@ -91,7 +91,7 @@ export const updateLOA = async ( export const removeLOA = async ( LOAData: { companyId: number | string; - loaId: string; + loaId: number; }, ): Promise> => { const { companyId, loaId } = LOAData; @@ -107,7 +107,7 @@ export const removeLOA = async ( * @returns A Promise containing the dms reference string for the LOA download stream */ export const downloadLOA = async ( - loaId: string, + loaId: number, companyId: string | number, ) => { const url = SPECIAL_AUTH_API_ROUTES.LOA.DOWNLOAD(companyId, loaId); @@ -123,7 +123,7 @@ export const downloadLOA = async ( export const removeLOADocument = async ( LOAData: { companyId: number | string; - loaId: string; + loaId: number; }, ): Promise> => { const { companyId, loaId } = LOAData; diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx index 8d90836ea..06ec26564 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx @@ -17,8 +17,8 @@ export const ExpiredLOAModal = ({ showModal: boolean; allowEditLOA: boolean; handleCancel: () => void; - handleEdit: (loaId: string) => void; - handleDownload: (loaId: string) => void; + handleEdit: (loaId: number) => void; + handleDownload: (loaId: number) => void; expiredLOAs: LOADetail[]; }) => { return ( diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx index 386b6215f..666f7a19c 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx @@ -7,18 +7,17 @@ export const LOADownloadCell = ({ onDownload, props: { row }, }: { - onDownload: (loaId: string) => void; + onDownload: (loaId: number) => void; props: { row: MRT_Row; }; }) => { - const loaId = `${row.original.loaId}`; const loaHasDocument = Boolean(row.original.documentId); return loaHasDocument ? ( onDownload(loaId)} + onClick={() => onDownload(row.original.loaId)} > Download Letter diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx index 0383c1db8..69d3910e8 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx @@ -28,11 +28,11 @@ export const LOAList = ({ loas: LOADetail[]; isActive: boolean; allowEditLOA: boolean; - onEdit: (loaId: string) => void; - onDelete?: (loaId: string) => void; - onDownload: (loaId: string) => void; + onEdit: (loaId: number) => void; + onDelete?: (loaId: number) => void; + onDownload: (loaId: number) => void; }) => { - const handleEditLOA = (loaId: string) => { + const handleEditLOA = (loaId: number) => { if (!allowEditLOA) return; onEdit(loaId); }; diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx index a365964ad..bec21ad11 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx @@ -8,8 +8,8 @@ import { LOADownloadCell } from "./LOADownloadCell"; export const LOAListColumnDef = ( allowEditLOA: boolean, - onEditLOA: (loaId: string) => void, - onDownload: (loaId: string) => void, + onEditLOA: (loaId: number) => void, + onDownload: (loaId: number) => void, ): MRT_ColumnDef[] => [ { Cell: ( diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx index b8184d0e6..4301b5c81 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx @@ -9,21 +9,19 @@ export const LOANumberCell = ({ props: { row }, }: { allowEditLOA: boolean; - onEditLOA: (loaId: string) => void; + onEditLOA: (loaId: number) => void; props: { row: MRT_Row; }; }) => { - const loaId = `${row.original.loaId}`; - const loaNumber = `${row.original.loaNumber}`; return allowEditLOA ? ( onEditLOA(loaId)} + onClick={() => onEditLOA(row.original.loaId)} > - {loaNumber} + {row.original.loaNumber} ) : ( - <>{loaNumber} + <>{row.original.loaNumber} ); }; diff --git a/frontend/src/features/settings/hooks/LOA.ts b/frontend/src/features/settings/hooks/LOA.ts index 75749bde1..bb3d8cb6e 100644 --- a/frontend/src/features/settings/hooks/LOA.ts +++ b/frontend/src/features/settings/hooks/LOA.ts @@ -12,7 +12,7 @@ import { const QUERY_KEYS = { LOAS: (expired: boolean) => ["loas", expired], - LOA: (loaId?: Nullable) => ["loa", loaId], + LOA: (loaId?: Nullable) => ["loa", loaId], }; /** @@ -37,7 +37,7 @@ export const useFetchLOAs = (companyId: number | string, expired: boolean) => { * @param loaId id of the LOA to fetch * @returns Query result of the LOA details */ -export const useFetchLOADetail = (companyId: number, loaId?: Nullable) => { +export const useFetchLOADetail = (companyId: number, loaId?: Nullable) => { return useQuery({ queryKey: QUERY_KEYS.LOA(loaId), queryFn: () => { diff --git a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx index 13d721c62..b2205fd39 100644 --- a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx +++ b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx @@ -26,7 +26,7 @@ export const LOASteps = ({ companyId, onExit, }: { - loaId?: Nullable; + loaId?: Nullable; companyId: number; onExit: () => void; }) => { diff --git a/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx b/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx index b3b53a0b2..410938068 100644 --- a/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx +++ b/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx @@ -46,9 +46,9 @@ export const SpecialAuthorizations = ({ const isLcvAllowed = getDefaultRequiredVal(false, specialAuthorizations?.isLcvAllowed); const [showExpiredLOAs, setShowExpiredLOAs] = useState(false); - const [loaToDelete, setLoaToDelete] = useState>(null); + const [loaToDelete, setLoaToDelete] = useState>(null); const [showLOASteps, setShowLOASteps] = useState(false); - const [loaToEdit, setLoaToEdit] = useState>(null); + const [loaToEdit, setLoaToEdit] = useState>(null); const { userClaims, @@ -143,7 +143,7 @@ export const SpecialAuthorizations = ({ setLoaToEdit(null); }; - const handleEditLOA = (loaId: string) => { + const handleEditLOA = (loaId: number) => { if (!canWriteLOA) return; setShowLOASteps(true); setLoaToEdit(loaId); @@ -156,7 +156,7 @@ export const SpecialAuthorizations = ({ expiredLOAsQuery.refetch(); }; - const handleOpenDeleteModal = (loaId: string) => { + const handleOpenDeleteModal = (loaId: number) => { if (!canWriteLOA) return; setLoaToDelete(loaId); }; @@ -165,7 +165,7 @@ export const SpecialAuthorizations = ({ setLoaToDelete(null); }; - const handleDeleteLOA = async (loaId: string) => { + const handleDeleteLOA = async (loaId: number) => { try { if (canWriteLOA) { await removeLOAMutation.mutateAsync({ @@ -182,7 +182,7 @@ export const SpecialAuthorizations = ({ } }; - const handleDownloadLOA = async (loaId: string) => { + const handleDownloadLOA = async (loaId: number) => { if (loaId && canReadLOA) { try { const { blobObj: blobObjWithoutType } = await downloadLOA( diff --git a/frontend/src/features/settings/types/SpecialAuthorization.ts b/frontend/src/features/settings/types/SpecialAuthorization.ts index 7766de8e7..4392c906c 100644 --- a/frontend/src/features/settings/types/SpecialAuthorization.ts +++ b/frontend/src/features/settings/types/SpecialAuthorization.ts @@ -1,3 +1,4 @@ +import { areArraysEqual } from "../../../common/helpers/util"; import { Nullable, RequiredOrNull } from "../../../common/types/common"; import { PermitType } from "../../permits/types/PermitType"; @@ -29,8 +30,8 @@ export const noFeePermitTypeDescription = (noFeePermitType: NoFeePermitType) => }; export interface LOADetail { - loaId: string; - loaNumber: string; + loaId: number; + loaNumber: number; companyId: number; startDate: string; expiryDate?: Nullable; @@ -40,6 +41,8 @@ export interface LOADetail { comment?: Nullable; powerUnits: string[]; trailers: string[]; + originalLoaId: number; + previousLoaId?: Nullable; } export interface CreateLOARequestData { @@ -62,6 +65,33 @@ export interface UpdateLOARequestData { trailers: string[]; } +/** + * Determine whether or not two LOAs have the same details. + * @param loa1 First LOA + * @param loa2 Second LOA + * @returns Whether or not the two LOAs have the same details + */ +export const areLOADetailsEqual = ( + loa1?: Nullable, + loa2?: Nullable, +) => { + if (!loa1 && !loa2) return true; + if (!loa1 || !loa2) return false; + + return loa1.loaId === loa2.loaId + && loa1.loaNumber === loa2.loaNumber + && loa1.companyId === loa2.companyId + && loa1.startDate === loa2.startDate + && loa1.expiryDate === loa2.expiryDate + && loa1.documentId === loa2.documentId + && loa1.fileName === loa2.fileName + && areArraysEqual(loa1.loaPermitType, loa2.loaPermitType) + && loa1.comment === loa2.comment + && areArraysEqual(loa1.powerUnits, loa2.powerUnits) + && areArraysEqual(loa1.trailers, loa2.trailers) + && loa1.originalLoaId === loa2.originalLoaId + && loa1.previousLoaId === loa2.previousLoaId; +}; export interface SpecialAuthorizationData { companyId: number; specialAuthId: number; diff --git a/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx b/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx index 4044f9ae3..b65330939 100644 --- a/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx +++ b/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx @@ -2,6 +2,7 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Card, CardContent, CardMedia, Stack } from "@mui/material"; import { BC_COLOURS } from "../../../themes/bcGovStyles"; +import { PPC_EMAIL, TOLL_FREE_NUMBER } from "../../../common/constants/constants"; /** * React component to display an info box about how to locate @@ -36,8 +37,8 @@ export const ClientAndPermitReferenceInfoBox = () => { If you need further assistance, please contact the
Provincial Permit Centre at{" "} - Toll-free: 1-800-559-9688 or
{" "} - Email: ppcpermit@gov.bc.ca + Toll-free: {TOLL_FREE_NUMBER} or
{" "} + Email: {PPC_EMAIL} , + @InjectRepository(PermitLoa) + private permitLoaRepository: Repository, + @InjectRepository(LoaDetail) + private loaDetail: Repository, private dataSource: DataSource, private readonly dopsService: DopsService, private readonly paymentService: PaymentService, @@ -1198,4 +1212,83 @@ export class ApplicationService { return result; } + @LogAsyncMethodExecution() + async createPermitLoa( + currentUser: IUserJWT, + permitId: string, + createPermitLoaDto: CreatePermitLoaDto, + ): Promise { + const { loaIds: inputLoaIds } = createPermitLoaDto; + const existingPermitLoa = await this.findAllPermitLoa(permitId); + const permit = await this.findOne(permitId); + const existingLoaIds = existingPermitLoa.map((x) => x.loaId); + const loaIdsToDelete = existingLoaIds.filter( + (value) => !inputLoaIds.includes(value), + ); + const loaIdsToInsert = inputLoaIds.filter( + (value) => !existingLoaIds.includes(value), + ); + + if (loaIdsToInsert.length) { + const loaDetails = await this.loaDetail.find({ + where: { + loaId: In(loaIdsToInsert), + company: { companyId: permit.company.companyId }, + }, + }); + if(loaDetails.length != loaIdsToInsert.length) + throw new BadRequestException('One or more loa(s) does not exist') + // Transform the permit LOA IDs from an array of numbers into individual records. + const singlePermitLoa = loaIdsToInsert.map((loaId) => ({ + permitId, + loaIds: [loaId], + })); + + const permitLoas = await this.classMapper.mapArrayAsync( + singlePermitLoa, + CreatePermitLoaDto, + PermitLoa, + { + extraArgs: () => ({ + permitId, + userName: currentUser.userName, + userGUID: currentUser.userGUID, + timestamp: new Date(), + directory: currentUser.orbcUserDirectory, + }), + }, + ); + + // Save new PermitLoas in bulk + await this.permitLoaRepository.save(permitLoas); + } + + // Delete old PermitLoas in a single query + if (loaIdsToDelete?.length) + await this.permitLoaRepository.delete({ + permitId: permitId, + loa: { loaId: In(loaIdsToDelete) }, + }); + return await this.findAllPermitLoa(permitId); + } + @LogAsyncMethodExecution() + async findAllPermitLoa(permitId: string): Promise { + const savedPermitLoa = await this.permitLoaRepository + .createQueryBuilder('permitLoa') + .innerJoinAndSelect('permitLoa.loa', 'loa') + .innerJoinAndSelect('loa.company', 'company') + .innerJoinAndSelect('loa.loaVehicles', 'loaVehicles') + .innerJoinAndSelect('loa.loaPermitTypes', 'loaPermitTypes') + .where('permitLoa.permitId = :permitId', { + permitId: permitId, + }) + .getMany(); + const readPermitLoaDto: ReadPermitLoaDto[] = + await this.classMapper.mapArrayAsync( + savedPermitLoa, + PermitLoa, + ReadPermitLoaDto, + ); + return readPermitLoaDto; + } } diff --git a/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts b/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts index 0e674868b..56610a8f2 100644 --- a/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts +++ b/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts @@ -52,6 +52,9 @@ import { ApplicationQueueStatus, convertApplicationQueueStatus, } from '../../../common/enum/case-status-type.enum'; +import { ApplicationIdIdPathParamDto } from './dto/request/pathParam/applicationId.path-params.dto'; +import { CreatePermitLoaDto } from './dto/request/create-permit-loa.dto'; +import { ReadPermitLoaDto } from './dto/response/read-permit-loa.dto'; @ApiBearerAuth() @ApiTags('Company Application') @@ -367,4 +370,32 @@ export class CompanyApplicationController { } return deleteResult; } + @ApiOperation({ + summary: 'Designate LoA to permit.', + description: + 'Designate LoA to permit. Returns the created permit LoA object from the database.', + }) + @ApiCreatedResponse({ + description: 'Permit Loa Details', + type: ReadPermitLoaDto, + isArray: true, + }) + @Permissions({ + allowedBCeIDRoles: CLIENT_USER_ROLE_LIST, + allowedIdirRoles: IDIR_USER_ROLE_LIST, + }) + @Post(':applicationId/loas') + async createPermitLoa( + @Req() request: Request, + @Param() { applicationId }: ApplicationIdIdPathParamDto, + @Body() createPermitLoaDto: CreatePermitLoaDto, + ): Promise { + const currentUser = request.user as IUserJWT; + const result = await this.applicationService.createPermitLoa( + currentUser, + applicationId, + createPermitLoaDto, + ); + return result; + } } diff --git a/vehicles/src/modules/permit-application-payment/application/dto/request/create-permit-loa.dto.ts b/vehicles/src/modules/permit-application-payment/application/dto/request/create-permit-loa.dto.ts new file mode 100644 index 000000000..fbecc9fdf --- /dev/null +++ b/vehicles/src/modules/permit-application-payment/application/dto/request/create-permit-loa.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ArrayMinSize, IsInt, IsPositive } from 'class-validator'; + +export class CreatePermitLoaDto { + @ApiProperty({ + description: 'Loa Ids to be assigned to the permit.', + isArray: true, + example: [1], + }) + @IsInt({ each: true }) + @IsPositive({ each: true }) + @ArrayMinSize(1) + loaIds: number[]; +} diff --git a/vehicles/src/modules/permit-application-payment/application/dto/response/read-permit-loa.dto.ts b/vehicles/src/modules/permit-application-payment/application/dto/response/read-permit-loa.dto.ts new file mode 100644 index 000000000..c68ade7f8 --- /dev/null +++ b/vehicles/src/modules/permit-application-payment/application/dto/response/read-permit-loa.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ReadLoaDto } from 'src/modules/special-auth/dto/response/read-loa.dto'; + +export class ReadPermitLoaDto extends ReadLoaDto { + @ApiProperty({ + description: 'Permit Loa id', + example: 1, + }) + permitLoaId: number; +} diff --git a/vehicles/src/modules/permit-application-payment/application/entities/permit-loa.entity.ts b/vehicles/src/modules/permit-application-payment/application/entities/permit-loa.entity.ts new file mode 100644 index 000000000..b9c76c214 --- /dev/null +++ b/vehicles/src/modules/permit-application-payment/application/entities/permit-loa.entity.ts @@ -0,0 +1,29 @@ +import { + Column, + Entity, + JoinColumn, + OneToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { AutoMap } from '@automapper/classes'; +import { Base } from 'src/modules/common/entities/base.entity'; +import { LoaDetail } from 'src/modules/special-auth/entities/loa-detail.entity'; + +@Entity({ name: 'permit.ORBC_PERMIT_LOA' }) +export class PermitLoa extends Base { + @AutoMap() + @PrimaryGeneratedColumn({ type: 'int', name: 'PERMIT_LOA_ID' }) + permitLoaId: number; + + @AutoMap(() => LoaDetail) + @OneToOne(() => LoaDetail, (LoaDetail) => LoaDetail.loaId) + @JoinColumn({ name: 'LOA_ID' }) + loa: LoaDetail; + + @AutoMap() + @Column({ + type: 'bigint', + name: 'PERMIT_ID', + }) + permitId: string; +} diff --git a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts index 1f63de139..9569c7c09 100644 --- a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts +++ b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts @@ -27,6 +27,10 @@ import { convertCaseStatus, } from '../../../../common/enum/case-status-type.enum'; import { ReadCaseActivityDto } from '../../../case-management/dto/response/read-case-activity.dto'; +import { CreatePermitLoaDto } from '../dto/request/create-permit-loa.dto'; +import { PermitLoa } from '../entities/permit-loa.entity'; +import { ReadPermitLoaDto } from '../dto/response/read-permit-loa.dto'; +import * as dayjs from 'dayjs'; @Injectable() export class ApplicationProfile extends AutomapperProfile { @@ -444,6 +448,135 @@ export class ApplicationProfile extends AutomapperProfile { }), ), ); + createMap( + mapper, + CreatePermitLoaDto, + PermitLoa, + forMember( + (d) => d.permitId, + mapWithArguments((_, { permitId }) => { + return permitId; + }), + ), + forMember( + (d) => d.loa.loaId, + mapFrom((s) => { + return s.loaIds[0]; + }), + ), + forMember( + (d) => d.createdUserGuid, + mapWithArguments((_, { userGUID }) => { + return userGUID; + }), + ), + forMember( + (d) => d.createdUser, + mapWithArguments((_, { userName }) => { + return userName; + }), + ), + forMember( + (d) => d.createdUserDirectory, + mapWithArguments((_, { directory }) => { + return directory; + }), + ), + + forMember( + (d) => d.createdDateTime, + mapWithArguments((_, { timestamp }) => { + return timestamp; + }), + ), + + forMember( + (d) => d.updatedUserGuid, + mapWithArguments((_, { userGUID }) => { + return userGUID; + }), + ), + forMember( + (d) => d.updatedUser, + mapWithArguments((_, { userName }) => { + return userName; + }), + ), + forMember( + (d) => d.updatedUserDirectory, + mapWithArguments((_, { directory }) => { + return directory; + }), + ), + + forMember( + (d) => d.updatedDateTime, + mapWithArguments((_, { timestamp }) => { + return timestamp; + }), + ), + ); + + createMap( + mapper, + PermitLoa, + ReadPermitLoaDto, + forMember( + (d) => d.permitLoaId, + mapFrom((s) => { + return s.permitLoaId; + }), + ), + forMember( + (d) => d.loaId, + mapFrom((s) => { + return s.loa.loaId; + }), + ), + forMember( + (d) => d.companyId, + mapFrom((s) => { + return s.loa.company.companyId; + }), + ), + forMember( + (d) => d.startDate, + mapFrom((s) => { + return dayjs(s.loa.startDate).format('YYYY-MM-DD'); + }), + ), + forMember( + (d) => d.expiryDate, + mapFrom((s) => { + if (s.loa.expiryDate) + return dayjs(s.loa.expiryDate).format('YYYY-MM-DD'); + }), + ), + forMember( + (d) => d.loaPermitType, + mapFrom((s) => { + return s.loa.loaPermitTypes.map((lpt) => lpt.permitType); + }), + ), + forMember( + (d) => d.powerUnits, + mapFrom((s) => { + if (s.loa.loaVehicles) + return s.loa.loaVehicles + .filter((lv) => lv.powerUnit) + .map((lv) => lv.powerUnit); + }), + ), + forMember( + (d) => d.trailers, + mapFrom((s) => { + if (s.loa.loaVehicles) + return s.loa.loaVehicles + .filter((lv) => lv.trailer) + .map((lv) => lv.trailer); + }), + ), + ); }; } } diff --git a/vehicles/src/modules/special-auth/loa.service.ts b/vehicles/src/modules/special-auth/loa.service.ts index 8363942d5..68c81028f 100644 --- a/vehicles/src/modules/special-auth/loa.service.ts +++ b/vehicles/src/modules/special-auth/loa.service.ts @@ -94,6 +94,7 @@ export class LoaService { companyId, savedLoaDetail.loaId, ); + const readLoaDto = await this.classMapper.mapAsync( refreshedLoaDetailsEntity, LoaDetail, @@ -173,7 +174,6 @@ export class LoaService { where: { loaId: loaId, company: { companyId: companyId }, - isActive: true, }, relations: ['company', 'loaVehicles', 'loaPermitTypes'], }); diff --git a/vehicles/src/modules/special-auth/profile/loa.profile.ts b/vehicles/src/modules/special-auth/profile/loa.profile.ts index a48e44ac0..0a6594429 100644 --- a/vehicles/src/modules/special-auth/profile/loa.profile.ts +++ b/vehicles/src/modules/special-auth/profile/loa.profile.ts @@ -9,11 +9,11 @@ import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; import { Injectable } from '@nestjs/common'; import { CreateLoaDto } from '../dto/request/create-loa.dto'; import { LoaDetail } from '../entities/loa-detail.entity'; -import { ReadLoaDto } from '../dto/response/read-loa.dto'; import { LoaPermitType } from '../entities/loa-permit-type-details.entity'; import { LoaVehicle } from '../entities/loa-vehicles.entity'; import * as dayjs from 'dayjs'; import { UpdateLoaDto } from '../dto/request/update-loa.dto'; +import { ReadLoaDto } from '../dto/response/read-loa.dto'; @Injectable() export class LoaProfile extends AutomapperProfile { @@ -385,7 +385,6 @@ export class LoaProfile extends AutomapperProfile { ), ), ); - createMap( mapper, LoaDetail, From ab0fb51afe269d70bf17b4d3b72eb1811a8fa26b Mon Sep 17 00:00:00 2001 From: John Fletcher <113134542+john-fletcher-aot@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:14:09 -0700 Subject: [PATCH 42/58] feat: Updates to CSV import and export for size dimension set into policy engine (#1639) --- policy-engine/package-lock.json | 14 +- policy-engine/package.json | 1 + .../Single Trip Oversize Dimension Set.csv | 214 + .../os-dimensions-simplified-nodefault.csv | 211 - .../_examples/os-dimensions-simplified.csv | 212 - policy-engine/src/_examples/os-dimensions.csv | 213 + .../src/_examples/output-os-as-csv.ts | 126 + .../src/_examples/update-os-from-csv.ts | 54 +- .../complete-in-progress.sample.ts | 8708 +++++++++-------- 9 files changed, 5136 insertions(+), 4617 deletions(-) create mode 100644 policy-engine/src/_examples/Single Trip Oversize Dimension Set.csv delete mode 100644 policy-engine/src/_examples/os-dimensions-simplified-nodefault.csv delete mode 100644 policy-engine/src/_examples/os-dimensions-simplified.csv create mode 100644 policy-engine/src/_examples/os-dimensions.csv create mode 100644 policy-engine/src/_examples/output-os-as-csv.ts diff --git a/policy-engine/package-lock.json b/policy-engine/package-lock.json index dbe272077..a3b850fa3 100644 --- a/policy-engine/package-lock.json +++ b/policy-engine/package-lock.json @@ -1,18 +1,19 @@ { "name": "onroute-policy-engine", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "onroute-policy-engine", - "version": "0.1.0", + "version": "0.2.0", "license": "Apache-2.0", "dependencies": { "dayjs": "^1.11.10", "json-rules-engine": "^6.5.0" }, "devDependencies": { + "@iwsio/json-csv-core": "^1.1.7", "@types/jest": "^29.5.12", "@typescript-eslint/eslint-plugin": "^7.5.0", "csv": "^6.3.10", @@ -880,6 +881,15 @@ "node": ">=8" } }, + "node_modules/@iwsio/json-csv-core": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@iwsio/json-csv-core/-/json-csv-core-1.1.7.tgz", + "integrity": "sha512-vI3Iy26ffmKJHUTSfwJdv4hEFI8vCemMQLDi0iMQBCYmyhsa2m0Fn8dSLy+FlyLnVqZRqyM8/yauwSXGFdnohg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@jest/console": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", diff --git a/policy-engine/package.json b/policy-engine/package.json index 7a846121b..415555bc5 100644 --- a/policy-engine/package.json +++ b/policy-engine/package.json @@ -26,6 +26,7 @@ "license": "Apache-2.0", "homepage": "https://github.com/bcgov/onroutebc#readme", "devDependencies": { + "@iwsio/json-csv-core": "^1.1.7", "@types/jest": "^29.5.12", "@typescript-eslint/eslint-plugin": "^7.5.0", "csv": "^6.3.10", diff --git a/policy-engine/src/_examples/Single Trip Oversize Dimension Set.csv b/policy-engine/src/_examples/Single Trip Oversize Dimension Set.csv new file mode 100644 index 000000000..3dfa49997 --- /dev/null +++ b/policy-engine/src/_examples/Single Trip Oversize Dimension Set.csv @@ -0,0 +1,214 @@ +,,,,,,Lower Mainland,,,Kootenay,,,Peace,,,BC Default,,,Projection,,,,, +No Self Issue,Commodity,Power Unit,Trailer,Allow Jeep,Allow Booster,Width,Height,Length,Width,Height,Length,Width,Height,Length,Width,Height,Length,ORBC FP,ORBC RP,Policy Ref,Edit,Notes, +,"Auto Carrier, Campers And Boats (Stinger Steered Transporters Only)",Truck Tractors - Stinger Steered,Semi-Trailers - Stinger Steered Automobile Transporters,,,2.6,4.3,25,2.6,4.3,25,2.6,4.88,25,2.6,4.4,25,1,1.2,4.2.7.A.1/4.2.7.B.A/ 5.3.11,,"Loaded dimensions used, equipment itself is less",FP and RP points can vary in CTPM (i.e. rear projection can be from turn centre or bumper or sometimes bunk!) +,Bridge Beams,Truck Tractors,Pole Trailers,X,,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,3,6.5,4.5.5,,"OAL could be greater with steering trailers, do we need two categories?", +,Bridge Beams,Truck Tractors,Semi-Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,5.33,31,3.2,4.3,31,3,6.5,4.2.7.A.13,,"KPLA limit of 18.3 m - impacts other non-reducible loads as well, can this be restricted? TPS currently cannot. (Peace River exempt)", +,Bridge Beams,Truck Tractors,Steering Dolly,,,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,3,6.5,5.3.12.F,,, +,Brushcutters (Peace Only),Truck Tractors,Semi-Trailers,,,2.6,4.15,23,2.6,4.15,23,4.57,5.33,23,2.6,4.15,23,3,6.5,4.2.7.B.B,,tractor semi-trailer could be 23.5 if a tridem , +,Brushcutters (Peace Only),Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,2.6,4.15,23,2.6,4.15,23,4.57,5.33,23,2.6,4.15,23,3,6.5,4.2.7.B.B,,, +,Brushcutters (Peace Only),Trucks,None,,,2.6,4.15,12.5,2.6,4.15,12.5,4.57,5.33,12.5,2.6,4.15,12.5,1,1,4.2.7.B.B,,, +,Brushcutters (Peace Only),Trucks,Pony Trailers,,,2.6,4.15,23,2.6,4.15,23,3.8,5.33,23,2.6,4.15,23,1,1,4.2.7.B.B/5.3.12.G,,, +,Doglogger/Sjostrum Trailers (decked),Truck Tractors,None,,,2.6,4.15,13.5,2.6,4.15,13.5,2.6,4.15,13.5,2.6,4.15,13.5,1,5,4.2.7.A.13,,, +,Empty,Truck Tractors,Logging Trailer - Overwidth Bunks,,,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,0,0,5.3.5.B,,, +,Empty,Picker Truck Tractors,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,3,6.5,5.3.8.B.,,, +X,Empty,Picker Truck Tractors,Platform Trailers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3,6.5,,,, +,Empty,Picker Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,2.6,4.15,25,2.6,4.15,25,2.6,4.15,27.5,2.6,4.15,25,3,6.5,5.3.9.A./5.3.9.B.,,, +X,Empty,Picker Truck Tractors,Semi-Trailers - Wheelers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3,6.5,,,, +X,Empty,Picker Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3,6.5,,,, +,Empty,Picker Truck,Pony Trailers,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,0,0,5.3.9.A,,, +X,Empty,Truck Tractors,Platform Trailers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3,6.5,,,, +,Empty,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,2.6,4.15,31,2.6,4.4,31,2.6,5.33,31,2.6,4.4,31,3,6.5,5.3.12.C.,,, +,Empty,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,2.6,4.15,27.5,2.6,4.4,27.5,2.6,5.33,27.5,2.6,4.4,27.5,3,6.5,5.3.12.B.,,, +,Empty,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.15,31,3.2,4.4,31,3.2,5.33,31,3.2,4.4,31,3,6.5,5.3.12.A.,,, +X,Empty,Truck Tractors,Semi-Trailers - Wheelers,X,X,3.2,4.15,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, +X,Empty,Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,3.2,4.15,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, +,Fixed Equipment,Truck Tractors,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,4,9.5,5.3.4.F.,,"OAL is not specified, limited by FPK, STWB, RPT", +,Fixed Equipment,Truck Tractors,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.D.,,18.3 KPLA allowed, +,Fixed Equipment,Truck Tractors,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,,4.26,4.72,,4.26,5.33,,4.26,4.72,,3,6.5,5.3.4.E.,,, +,Fixed Equipment,Truck Tractors,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.A.,,, +X,Fixed Equipment,Truck Tractors,Fixed Equipment - Wheeler Semi-Trailers,X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.B.,,, +,Fixed Equipment,Trucks,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,,3.2,4.3,,3.2,4.3,,3.2,4.3,,4,9.5,5.3.4.F.,,"Check with Barry, does this exist? I don't believe we intended pony trailer to get allowances", +,Fixed Equipment,Trucks,Fixed Equipment - Counter Flow Asphalt Drum Mixers,,,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.D.,,"Check with Barry, does this exist? I don't believe we intended pony trailer to get allowances", +,Fixed Equipment,Trucks,Fixed Equipment - Pony Trailers,,,3.2,4.3,,3.2,4.3,,3.2,4.3,,3.2,4.3,,1,4,5.3.4.C.,,, +,Fixed Equipment,Trucks,Fixed Equipment - Portable Asphalt Baghouses,,,4.26,4.72,,4.26,4.72,,4.26,4.72,,4.26,4.72,,3,6.5,5.3.4.E.,,"Check with Barry, does this exist? I don't believe we intended pony trailer to get allowances", +,Garbage Bins,Trucks,Full Trailers,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,3,6.5,4.2.7.A.7,,, +,Garbage Bins,Trucks,None,,,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,3,6.5,4.2.7.A.7,,, +,Garbage Bins,Trucks,Pony Trailers,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,3,6.5,4.2.7.A.7,,, +,"Grader, Tractor Blades, Buckets",Fixed Equipment - Trucks/Graders etc.,None,,,4.4,4.15,12.5,4.4,4.15,12.5,4.4,4.15,12.5,4.4,4.15,12.5,0,0,4.2.7.B.C,,, +,Hay Bales (Round) Peace River Only,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,1,1,4.2.7.A.8/4.4.2,,, +,Hay Bales (Round) Peace River Only,Truck Tractors,Semi-Trailers - B-Trains,,,3.5,4.3,27.5,3.5,4.3,27.5,3.84,4.8,27.5,3.5,4.3,27.5,1,1,4.2.7.A.8/4.4.2,,, +,Hay Bales (Round) Peace River Only,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, +,Hay Bales (Round) Peace River Only,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, +,Hay Bales (Round) Peace River Only,Trucks,Full Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, +,Hay Bales (Round) Peace River Only,Trucks,None,,,3.5,4.3,12.5,3.5,4.3,12.5,3.84,4.8,12.5,3.5,4.3,12.5,1,1,4.2.7.A.8/4.4.2,,, +,Hay Bales (Round) Peace River Only,Trucks,Pony Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, +,Hay Bales Large Rectangular,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,1,1,4.2.7.A.8/4.4.1,,, +,Hay Bales Large Rectangular,Truck Tractors,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,1,1,4.2.7.A.8/4.4.1,,, +,Hay Bales Large Rectangular,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.4,23,3,6.5,4.2.7.A.8/4.4.1,,, +,Hay Bales Large Rectangular,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,3,6.5,4.2.7.A.8/4.4.1,,, +,Hay Bales Large Rectangular,Trucks,Full Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, +,Hay Bales Large Rectangular,Trucks,None,,,3.05,4.3,12.5,3.05,4.3,12.5,3.05,4.8,12.5,3.05,4.3,12.5,1,1,4.2.7.A.8/4.4.1,,, +,Hay Bales Large Rectangular,Trucks,Pony Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, +,Hay Bales Round,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,1,1,4.2.7.A.8/4.4.2,,, +,Hay Bales Round,Truck Tractors,Semi-Trailers - B-Trains,,,3.5,4.3,27.5,3.5,4.3,27.5,3.84,4.8,27.5,3.5,4.3,27.5,1,1,4.2.7.A.8/4.4.2,,, +,Hay Bales Round,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, +,Hay Bales Round,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, +,Hay Bales Round,Trucks,Full Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, +,Hay Bales Round,Trucks,None,,,3.5,4.3,12.5,3.5,4.3,12.5,3.84,4.8,12.5,3.5,4.3,12.5,1,1,4.2.7.A.8/4.4.2,,, +,Hay Bales Round,Trucks,Pony Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, +,Hay Bales Small Rectangular,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,1,1,4.2.7.A.8/4.4.1,,, +,Hay Bales Small Rectangular,Truck Tractors,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,1,1,4.2.7.A.8/4.4.1,,, +,Hay Bales Small Rectangular,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,3,6.5,4.2.7.A.8/4.4.1,,, +,Hay Bales Small Rectangular,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,3,6.5,4.2.7.A.8/4.4.1,,, +,Hay Bales Small Rectangular,Trucks,Full Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, +,Hay Bales Small Rectangular,Trucks,None,,,3.05,4.3,12.5,3.05,4.3,12.5,3.05,4.8,12.5,3.05,4.3,12.5,1,1,4.2.7.A.8/4.4.1,,, +,Hay Bales Small Rectangular,Trucks,Pony Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, +,Hayrack Semi-Trailer with a Folded Chassis/Empty Piggyback,Truck Tractors,Semi-Trailers - Logging,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,1,5,4.5.6.E.,,, +,Intermodal Containers,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,2.6,4.4,26,2.6,4.4,26,2.6,4.4,26,2.6,4.4,26,1,1,4.2.7.A.9,,, +,Intermodal Containers,Truck Tractors,Semi-Trailers - B-Trains,,,2.6,4.4,27.5,2.6,4.4,27.5,2.6,4.4,27.5,2.6,4.4,27.5,1,1,4.2.7.A.9,,, +,Intermodal Containers,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,2.6,4.4,23,2.6,4.4,23,2.6,4.4,23,2.6,4.4,23,1,1,4.2.7.A.9,,, +,Intermodal Containers,Trucks,None,,,2.6,4.4,12.5,2.6,4.4,12.5,2.6,4.4,12.5,2.6,4.4,12.5,1,1,4.2.7.A.9,,, +,Intermodal Containers without Sides,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,4.4,4.72,23,4.4,4.72,23,4.4,4.72,23,4.4,4.72,23,1,1,4.2.7.10/4.2.7.B.D.,,, +,Laminated Beams,Truck Tractors,Pole Trailers,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,3,6.5,4.5.3,,, +,Laminated Beams,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,3,6.5,4.5.3,,, +X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,"Logging Trailer - Full Trailers, Tri Axle, Quad Axle",,,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,10,10,4.5.2,,, +X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Pole Trailers,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,8,9,4.5.2,,, +,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Steering Trailers - Manned,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,10,10,5.3.12.F,,, +,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Steering Trailers - Self/Remote,X,X,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,10,10,5.3.12.F,,, +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Full Trailers,,,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,3,6.5,4.5.1,,, +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Legacy Logging Trailer Combinations,,,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,3,5,5.3.7.C.,,, +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Pole Trailers,X,,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,3,6.5,4.5.1,,, +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,2.9,4.15,26,2.9,4.15,26,2.9,4.15,26,2.9,4.15,26,3,6.5,4.5.1,,, +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - B-Trains,,,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,3,6.5,4.5.1,,, +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,,,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,3,6.5,4.5.1,,, +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - Logging,,,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,3,6.5,4.5.1,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,Semi-Trailers,X,X,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,3.0,7.5,4.3.5,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,3.0,7.5,4.3.5,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Dollies,,,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,1,6.9,4.3,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Float Trailers,,,5.0,4.57,31.5,5.0,4.57,31.5,5.0,5.33,36,5.0,4.57,31.5,1,6.9,4.3.5.F,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Pony Trailers,,,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,1,6.9,4.3,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,Semi-Trailers,X,X,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,3.0,7.5,4.3.5,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,3.0,7.5,4.3.5,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Dollies,,,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,1,6.9,4.3,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Float Trailers,,,6.0,4.57,31.5,6.0,4.57,31.5,6.1,5.33,36,6.0,4.57,31.5,1,6.9,4.3.5.F,,, +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Pony Trailers,,,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,1,6.9,4.3,,, +,None,Concrete Pumper Trucks,None,,X,2.6,4.15,15.5,2.6,4.15,15.5,2.6,4.15,15.5,2.6,4.15,15.5,3,6.5,5.3.2.A.,,, +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.B.,,, +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.B.,,, +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.A.,,, +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.A.,,, +,None,Double Decker Buses,None,,,2.6,4.3,12.5,2.6,4.3,12.5,2.6,4.3,12.5,2.6,4.42,12.5,1,1,5.3.1.A,,, +,None,Fixed Equipment - Trucks/Graders etc.,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,1,4,,,, +X,None,Fixed Equipment - Trucks/Graders etc.,None,,,3.5,4.3,12.5,3.5,4.3,12.5,3.5,5.33,12.5,3.5,4.4,12.5,1,1,5.3.4.G.,,, +X,None,Inter-City Bus (Pulling Pony Trailer),Pony Trailers,,,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,1,1,5.3.1.B,,, +,None,Logging Trucks - Off-Highway,Semi-Trailers - Logging,,,4.4,4.15,23,4.4,4.15,23,4.4,4.15,23,4.4,4.15,23,3,6.5,5.3.5.A.,,, +X,None,Long Combination Vehicles (LCV) - Rocky Mountain Doubles,Semi-Trailers,,,2.6,4.15,32,2.6,4.15,32,2.6,4.15,31,2.6,4.15,32,1,1,5.3.6.D.,,, +,None,Long Combination Vehicles (LCV) - Turnpike Doubles,Semi-Trailers,,,2.6,4.15,41,2.6,4.15,41,2.6,4.15,41,2.6,4.15,41,1,1,5.3.6.D.,,, +X,None,Long Wheelbase Truck Tractors Exceeding 6.2 m up to 7.25 m,Semi-Trailers,X,X,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,1,1,5.3.7.A.,,, +,None,Picker Truck Tractors,None,,,2.6,4.15,16,2.6,4.15,16,2.6,4.15,16,2.6,4.15,16,3,1,5.3.9,,, +,None,Picker Truck Tractors,Semi-Trailers with Crane,X,X,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,3,6.5,5.3.9,,, +,None,Picker Truck Tractors,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,3,6.5,,,, +,None,Picker Truck Tractors,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,3,6.5,,,, +,None,Scrapers,None,,,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,1,1,5.3.10.A.1),,, +,None,Truck Tractors,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,4,9.5,,,, +,None,Truck Tractors,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,,,, +X,None,Truck Tractors,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,31,4.26,4.72,31,4.26,5.33,31,4.26,4.72,31,3,6.5,,,, +X,None,Truck Tractors,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,,,, +,None,Truck Tractors,Fixed Equipment - Wheeler Semi-Trailers,X,X,5,4.88,31,5,4.88,31,5,5.33,31,5,4.88,31,3,6.5,,,, +X,None,Truck Tractors,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,1,1,5.3.12.D.,,, +,None,Truck Tractors,Ready Mix Concrete Pump Semi-Trailers,X,X,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,3,6.5,5.3.2.B.,,, +,None,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,2.6,4.3,23,2.6,4.3,23,2.6,5.33,31,2.6,4.3,31,3,6.5,5.3.12.B.,,, +,None,Truck Tractors,Semi-Trailers - Insulated Vans with Reefer/Refrigeration Units,,,2.6,4.3,23,2.6,4.3,23,2.6,4.3,23,2.6,4.3,23,1,1,5.3.12.H.,,, +,None,Truck Tractors,Semi-Trailers - Non-Tac Short Chassis,,,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,5,2.25,5.3.7.D.,,, +,None,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,32,3.2,4.3,27.5,3,6.5,5.3.12.A.,,, +,None,Truck Tractors,Steering Trailers - Manned,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,1,1,5.3.12.F.ii),,, +,None,Truck Tractors,Steering Trailers - Self/Remote,X,X,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,1,1,5.3.12.F.i),,, +,None,Trucks,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,4,9.5,,,, +,None,Trucks,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,1,4,,,, +,None,Trucks,Full Trailers ,,,3.8,4.3,23,3.8,4.3,23,3.8,5.33,23,3.8,4.3,23,1,6.5,5.3.12.G.,,, +,None,Trucks,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW) with Attached Axles",,,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,1,6.9,"4.3.5.A, 4.3.5.B, 4.3.5.C",,, +,None,Trucks,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW) with Attached Axles",,,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,1,6.9,"4.3.5.D, 4.3.5.E",,, +,None,Trucks,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5,,,, +,None,Trucks,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,1,1,5.3.12.D.,,, +,None,Trucks,Park Model Homes with Attached Axles,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, +,None,Trucks,Pony Trailers,,,3.2,4.3,23,3.2,4.3,23,3.2,5.33,23,3.2,4.3,23,1,6.5,5.3.12.G.,,, +,None,Trucks Equipped with Front or Underbody Plow Blades,None,,,3.2,4.15,12.5,3.2,4.15,12.5,3.2,4.15,12.5,3.2,4.15,12.5,1,1,5.3.13,,, +X,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Logging,,,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,3,6.5,5.3.9.A./5.3.9.B.,,, +X,Non-Reducible Loads,Picker Truck Tractors,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers,X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,6.5,5.3.9.A./5.3.9.B.,non compliant vehicle,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,6.5,5.3.9.A./5.3.9.B.,,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,6.5,5.3.9.A./5.3.9.B.,,, +X,Non-Reducible Loads,Picker Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,6.5,5.3.9.A./5.3.9.B.,,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, +X,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, +,Non-Reducible Loads,Picker Truck Tractors,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,3,6.5,,,, +,Non-Reducible Loads,Picker Truck Tractors,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,3,6.5,,,, +X,Non-Reducible Loads,Truck Tractors,Platform Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,5,4.4,26,5,4.4,26,5,5.33,26,5,4.4,26,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - B-Trains,,,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +X,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Non-Reducible Loads,Truck Tractors,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +X,Non-Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers,,,2.6,4.88,25,2.6,4.88,25,2.6,5.33,25,2.6,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, +,Non-Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers - Stinger Steered Automobile Transporters,,,2.6,4.88,25,2.6,4.88,25,2.6,5.33,25,2.6,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, +,Non-Reducible Loads,Trucks,Dollies,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, +,Non-Reducible Loads,Trucks,Full Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G,,, +,Non-Reducible Loads,Trucks,None,,,5,4.88,16,5,4.88,16,5,5.33,16,5,4.88,16,1,6.5,4.2.7.B.G,,, +,Non-Reducible Loads,Trucks,Pony Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G,,, +,Oil Field Equipment,Oil and Gas - Bed Trucks,Expando Semi-Trailers,X,X,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,3,6.5,5.3.8.C.,,, +,Oil Field Equipment,Oil and Gas - Bed Trucks,None,,,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,1,5,5.3.8.A.i)/ii),,, +,Oil Field Equipment,Oil and Gas - Bed Trucks,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, +,Oil Field Equipment,Oil and Gas - Oilfield Sows,None,,,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,1,5,5.3.8.D.,,, +,Oil Field Equipment,Oil and Gas - Oilfield Sows,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3,6.5,5.3.8.E.,,, +,Oil Field Equipment,Oil and Gas - Service Rigs,None,,,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,10,6.5,5.3.8.F.,,, +,Oil Field Equipment,Oil and Gas - Service Rigs,Pony Trailers,,,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,10,6.5,5.3.8.F.i),,, +x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",None,,,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,10,6.5,5.3.8.F.,,, +x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",Pony Trailers,,,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,10,6.5,5.3.8.F.i),,, +,Oil Field Equipment,Truck Tractors,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, +,Park Model Homes,Trucks,Dollies,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, +,Park Model Homes,Trucks,Float Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, +,Park Model Homes,Trucks,Pony Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, +,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3,6.5,4.2.5.A.ii)/4.2.7.A.14,,, +,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3,6.5,4.2.5.A.ii),,, +,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3,6.5,4.2.7.B.H,,, +X,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3,6.5,4.2.7.B.H,,, +,Reducible Loads,Truck Tractors,Semi-Trailers - Logging,,,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Picker Truck Tractors,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Picker Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +X,Reducible Loads,Truck Tractors,Platform Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.2,4.3,26,3.2,4.3,26,3.2,5.33,26,3.2,4.3,26,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Semi-Trailers - B-Trains,,,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +X,Reducible Loads,Truck Tractors,Semi-Trailers - Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +X,Reducible Loads,Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Semi-Trailers with Crane,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +X,Reducible Loads,Truck Tractors,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,5.33,40,3.2,4.3,40,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors,Steering Trailers - Self/Remote,X,X,3.2,4.3,36,3.2,4.3,36,3.2,5.33,36,3.2,4.3,36,3,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers,,,2.6,4.3,25,2.6,4.3,25,2.6,5.33,25,2.6,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers - Stinger Steered Automobile Transporters,,,2.6,4.3,25,2.6,4.3,25,2.6,5.33,25,2.6,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Trucks,Dollies,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Trucks,Full Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Trucks,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5,4.2.4/4.2.5i)/4.2.6,,, +,Reducible Loads,Trucks,Pony Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, +X,Scraper on Dollies,Truck Tractors,Dollies,X,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,1,5.3.10.B.,,, +,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Truck Tractors,None,,,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,1,5,4.2.7.A.6/4.5.6.C.,,, +,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Trucks,None,,,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,1,5,4.2.7.A.6/4.5.6.C.,,, +,Tow Trucks And Disabled Vehicles,Tow Vehicles,None,,,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,1,1,5.3.14,,, +,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Truck Tractors,None,,,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,1,5,4.2.7.A.6/4.5.6.D.,,, +,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Trucks,None,,,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,1,5,4.2.7.A.6/4.5.6.D.,,, +,"Wood Chips, Residuals",Truck Tractors,Semi-Trailers - B-Trains,,,2.6,4.45,27.5,2.6,4.45,27.5,2.6,4.45,27.5,2.6,4.45,27.5,1,1,4.2.7.A.16/4.5.4,,, diff --git a/policy-engine/src/_examples/os-dimensions-simplified-nodefault.csv b/policy-engine/src/_examples/os-dimensions-simplified-nodefault.csv deleted file mode 100644 index 30ffc16eb..000000000 --- a/policy-engine/src/_examples/os-dimensions-simplified-nodefault.csv +++ /dev/null @@ -1,211 +0,0 @@ -,"Auto Carrier, Campers And Boats (Stinger Steered Transporters Only)",Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers - Stinger Steered Automobile Transporters,,,,4.3,25,,4.3,25,,4.88,25,,4.4,25,1,1.2,4.2.7.A.1/4.2.7.B.A/ 5.3.11,,"Loaded dimensions used, equipment itself is less",FP and RP points can vary in CTPM (i.e. rear projection can be from turn centre or bumper or sometimes bunk!) -,Bridge Beams,Truck Tractors,Mandatory,Pole Trailers,X,,,,31,,,31,,,31,,,31,3,6.5,4.5.5/5.3.12.F.(over 31m use Non-Reducible with Semi-Trailers - Steering Trailers),,"OAL could be greater with steering trailers, do we need two categories?", -,Brushcutters (Peace Only),Truck Tractors,Mandatory,Semi-Trailers,,,,,,,,,4.57,5.33,,,,,3,6.5,4.2.7.B.B,,tractor semi-trailer could be 23.5 if a tridem , -,Brushcutters (Peace Only),Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,,,,,,,4.57,5.33,,,,,3,6.5,4.2.7.B.B,,, -,Brushcutters (Peace Only),Trucks,Prohibited,None,,,,,,,,,4.57,5.33,,,,,,,4.2.7.B.B,,, -,Brushcutters (Peace Only),Trucks,Mandatory,Pony Trailers,,,,,,,,,3.8,5.33,,,,,,,4.2.7.B.B/5.3.12.G,,, -,Doglogger/Sjostrum Trailers (decked),Truck Tractors,Mandatory,None,,,,,13.5,,,13.5,,,13.5,,,13.5,,5,4.2.7.A.13,,, -,Empty,Truck Tractors,Mandatory,Logging Trailer - Overwidth Bunks,,,3.2,,,3.2,,,3.2,,,3.2,,,0,0,5.3.5.B,,, -,Empty,Picker Truck Tractors,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.2,4.3,23,3.2,4.3,23,3.2,4.3,23,3.2,4.3,23,3,6.5,5.3.8.B.,,, -X,Empty,Picker Truck Tractors,Mandatory,Platform Trailers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,,,,,,,,,27.5,,,25,3,6.5,5.3.9.A./5.3.9.B.,,, -X,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -X,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -X,Empty,Truck Tractors,Mandatory,Platform Trailers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -,Empty,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,,,31,,,31,,,31,,,31,3,6.5,5.3.12.C.,,, -,Empty,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,,,27.5,,,27.5,,,27.5,,,27.5,3,6.5,5.3.12.B.,,, -,Empty,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,,31,3.2,,31,3.2,,31,3.2,,31,3,6.5,5.3.12.A.,,, -X,Empty,Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -X,Empty,Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -,Empty,Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,,,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,4,9.5,5.3.4.F.,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.D.,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,31,4.26,4.72,31,4.26,5.33,31,4.26,4.72,31,3,6.5,5.3.4.E.,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.A.,,, -X,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Wheeler Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.B.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,4,9.5,5.3.4.F.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Counter Flow Asphalt Drum Mixers,,,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.D.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,,4,5.3.4.C.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Portable Asphalt Baghouses,,,4.26,4.72,31,4.26,4.72,31,4.26,4.72,31,4.26,4.72,31,3,6.5,5.3.4.E.,,, -,Garbage Bins,Trucks,Mandatory,Full Trailers,,,,,,,,,,,,,,,3,6.5,4.2.7.A.7,,, -,Garbage Bins,Trucks,Prohibited,None,,,,,,,,,,,,,,,3,6.5,4.2.7.A.7,,, -,Garbage Bins,Trucks,Mandatory,Pony Trailers,,,,,,,,,,,,,,,3,6.5,4.2.7.A.7,,, -,"Grader, Tractor Blades, Buckets",Fixed Equipment - Trucks/Graders etc.,Prohibited,None,,,4.4,,,4.4,,,4.4,,,4.4,,,,,4.2.7.B.C,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,,,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Trucks,Mandatory,Full Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Trucks,Prohibited,None,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Trucks,Mandatory,Pony Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,,,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,,,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.4,,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Trucks,Mandatory,Full Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Trucks,Prohibited,None,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Trucks,Mandatory,Pony Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1,,, -,Hay Bales Round,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,,,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.5,4.3,27.5,3.5,4.3,27.5,3.84,4.8,27.5,3.5,4.3,27.5,,,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Trucks,Mandatory,Full Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Trucks,Prohibited,None,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Trucks,Mandatory,Pony Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,,,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,,,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Trucks,Mandatory,Full Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Trucks,Prohibited,None,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Trucks,Mandatory,Pony Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1,,, -,Hayrack Semi-Trailer with a Folded Chassis/Empty Piggyback,Truck Tractors,Mandatory,Semi-Trailers - Logging,,,,,,,,,,,,,4.15,,,5,4.5.6.E.,,, -,Intermodal Containers,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,,4.4,26,,4.4,26,,4.4,26,,4.4,26,,,4.2.7.A.9,,, -,Intermodal Containers,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,,4.4,27.5,,4.4,27.5,,4.4,27.5,,4.4,27.5,,,4.2.7.A.9,,, -,Intermodal Containers,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,,4.4,,,4.4,,,4.4,,,4.4,,,,4.2.7.A.9,,, -,Intermodal Containers,Trucks,Mandatory,None,,,,4.4,,,4.4,,,4.4,,,4.4,,,,4.2.7.A.9,,, -,Intermodal Containers without Sides,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,4.4,4.72,,4.4,4.72,,4.4,4.72,,4.4,4.72,,,,4.2.7.10/4.2.7.B.D.,,, -,Laminated Beams,Truck Tractors,Mandatory,Pole Trailers,X,X,,,40,,,40,,,40,,,40,3,6.5,4.5.3,,, -,Laminated Beams,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,,,31,,,31,,,31,,,31,3,6.5,4.5.3,,, -X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,"Logging Trailer - Full Trailers, Tri Axle, Quad Axle",,,,,40,,,40,,,40,,,40,10,10,4.5.2,,, -X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,Pole Trailers,X,X,,,40,,,40,,,40,,,40,8,9,4.5.2,,, -,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,,,40,,,40,,,40,,,40,10,10,5.3.12.F,,, -,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,,,36,,,36,,,36,,,36,10,10,5.3.12.F,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Full Trailers,,,2.9,,27.5,2.9,,27.5,2.9,,27.5,2.9,,27.5,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Legacy Logging Trailer Combinations,,,2.6,,25,2.6,,25,2.6,,25,2.6,,25,3,5,5.3.7.C.,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Pole Trailers,X,,2.9,,27.5,2.9,,27.5,2.9,,27.5,2.9,,27.5,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,2.9,,26,2.9,,26,2.9,,26,2.9,,26,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,2.9,,27.5,2.9,,27.5,2.9,,27.5,2.9,,27.5,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,,,2.9,,25,2.9,,25,2.9,,25,2.9,,25,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - Logging,,,2.9,,25,2.9,,25,2.9,,25,2.9,,25,3,6.5,4.5.1,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,Mandatory,Semi-Trailers,X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Mandatory,Dollies,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Mandatory,Float Trailers,,,,4.57,,,4.57,,,5.33,36,,4.57,31.5,1,6.9,4.3.5.F,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Mandatory,Pony Trailers,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,Mandatory,Semi-Trailers,X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Mandatory,Dollies,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Mandatory,Float Trailers,,,,4.57,,,4.57,,,5.33,36,,4.57,31.5,1,6.9,4.3.5.F,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Mandatory,Pony Trailers,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3,,, -,None,Concrete Pumper Trucks,Prohibited,None,,X,,,15.5,,,15.5,,,15.5,,,15.5,3,6.5,5.3.2.A.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",Prohibited,Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.B.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",Prohibited,None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.B.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",Prohibited,Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.A.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",Prohibited,None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.A.,,, -,None,Double Decker Buses,Prohibited,None,,,,4.3,,,4.3,,,4.3,,,4.42,,,,5.3.1.A,,, -,None,Fixed Equipment - Trucks/Graders etc.,Prohibited,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,,4,,,, -X,None,Fixed Equipment - Trucks/Graders etc.,Prohibited,None,,,3.5,4.3,,3.5,4.3,,3.5,5.33,,3.5,4.4,12.5,,,5.3.4.G.,,, -X,None,Inter-City Bus (Pulling Pony Trailer),Mandatory,Pony Trailers,,,,,,,,,,,,,,,,,5.3.1.B,,, -,None,Logging Trucks - Off-Highway,Mandatory,Semi-Trailers - Logging,,,4.4,,,4.4,,,4.4,,,4.4,,,3,6.5,5.3.5.A.,,, -X,None,Long Combination Vehicles (LCV) - Rocky Mountain Doubles,Prohibited,Semi-Trailers,,,,,32,,,32,,,31,,,32,,,5.3.6.D.,,, -,None,Long Combination Vehicles (LCV) - Turnpike Doubles,Prohibited,Semi-Trailers,,,,,41,,,41,,,41,,,41,,,5.3.6.D.,,, -X,None,Long Wheelbase Truck Tractors Exceeding 6.2 m up to 7.25 m,Mandatory,Semi-Trailers,X,X,,,,,,,,,,2.6,4.15,23,,,5.3.7.A.,,, -,None,Picker Truck Tractors,Prohibited,None,,,,,16,,,16,,,16,2.6,4.15,16,3,,5.3.9,,, -,None,Picker Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,,,25,,,25,,,25,2.6,4.15,25,3,6.5,5.3.9,,, -,None,Picker Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,,,,,, -,None,Picker Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,,,,,, -,None,Scrapers,Prohibited,None,,,,,,,,,,,,,,12.5,,,5.3.10.A.1),,, -,None,Truck Tractors,Mandatory,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,4,9.5,,,, -,None,Truck Tractors,Mandatory,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,,,, -X,None,Truck Tractors,Mandatory,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,31,4.26,4.72,31,4.26,5.33,31,4.26,4.72,31,3,6.5,,,, -X,None,Truck Tractors,Mandatory,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,,,, -,None,Truck Tractors,Mandatory,Fixed Equipment - Wheeler Semi-Trailers,X,X,5,4.88,31,5,4.88,31,5,5.33,31,5,4.88,31,,,,,, -X,None,Truck Tractors,Mandatory,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,,,3.65,,,3.65,,,3.65,,,,,5.3.12.D.,,, -,None,Truck Tractors,Mandatory,Ready Mix Concrete Pump Semi-Trailers,X,X,,,,,,,,,,,,,3,6.5,5.3.2.B.,,, -,None,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,,4.3,,,4.3,,,5.33,,,4.3,31,3,6.5,5.3.12.B.,,, -,None,Truck Tractors,Mandatory,Semi-Trailers - Insulated Vans with Reefer/Refrigeration Units,,,,4.3,,,4.3,,,4.3,,,4.3,,,,5.3.12.H.,,, -,None,Truck Tractors,Mandatory,Semi-Trailers - Non-Tac Short Chassis,,,,,,,,,,,,,,,,,5.3.7.D.,,, -,None,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,32,3.2,4.3,27.5,3,6.5,5.3.12.A.,,, -,None,Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,,,40,,,40,,,40,,4.15,40,,,5.3.12.F.ii),,, -,None,Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,,,36,,,36,,,36,,,36,,,5.3.12.F.i),,, -,None,Trucks,Mandatory,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,4,9.5,,,, -,None,Trucks,Mandatory,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,,4,,,, -,None,Trucks,Mandatory,Full Trailers ,,,3.8,4.3,,3.8,4.3,,3.8,5.33,,3.8,4.3,,,6.5,5.3.12.G.,,, -,None,Trucks,Mandatory,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW) with Attached Axles",,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,"4.3.5.A, 4.3.5.B, 4.3.5.C",,, -,None,Trucks,Mandatory,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW) with Attached Axles",,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,"4.3.5.D, 4.3.5.E",,, -,None,Trucks,Mandatory,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5,,,, -,None,Trucks,Mandatory,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,,,3.65,,,3.65,,,3.65,,,,,5.3.12.D.,,, -,None,Trucks,Mandatory,Park Model Homes with Attached Axles,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -,None,Trucks,Mandatory,Pony Trailers,,,3.2,4.3,,3.2,4.3,,3.2,5.33,,3.2,4.3,,,6.5,5.3.12.G.,,, -,None,Trucks Equipped with Front or Underbody Plow Blades,Prohibited,None,,,3.2,,,3.2,,,3.2,,,3.2,,,,,5.3.13,,, -X,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Logging,,,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,,,5.3.9.A./5.3.9.B.,,, -X,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers,X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,,5.3.9.A./5.3.9.B.,non compliant vehicle,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,,5.3.9.A./5.3.9.B.,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,,5.3.9.A./5.3.9.B.,,, -X,Non-Reducible Loads,Picker Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,,5.3.9.A./5.3.9.B.,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,,,, -X,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,,,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,,,,,, -X,Non-Reducible Loads,Truck Tractors,Mandatory,Platform Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,5,4.4,26,5,4.4,26,5,5.33,26,5,4.4,26,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -X,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,,,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,,,4.2.4/4.2.5i)/4.2.6,,, -X,Non-Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers,,,,4.88,25,,4.88,25,,5.33,25,,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, -,Non-Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers - Stinger Steered Automobile Transporters,,,,4.88,25,,4.88,25,,5.33,25,,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, -,Non-Reducible Loads,Trucks,Mandatory,Dollies,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, -,Non-Reducible Loads,Trucks,Mandatory,Full Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G,,, -,Non-Reducible Loads,Trucks,Prohibited,None,,,5,4.88,16,5,4.88,16,5,5.33,16,5,4.88,16,1,6.5,4.2.7.B.G,,, -,Non-Reducible Loads,Trucks,Mandatory,Pony Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G,,, -,Oil Field Equipment,Oil and Gas - Bed Trucks,Mandatory,Expando Semi-Trailers,X,X,,4.3,27.5,,4.3,27.5,,4.3,27.5,,4.3,27.5,3,6.5,5.3.8.C.,,, -,Oil Field Equipment,Oil and Gas - Bed Trucks,Prohibited,None,,,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,,5,5.3.8.A.i)/ii),,, -,Oil Field Equipment,Oil and Gas - Bed Trucks,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, -,Oil Field Equipment,Oil and Gas - Oilfield Sows,Prohibited,None,,,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,,5,5.3.8.D.,,, -,Oil Field Equipment,Oil and Gas - Oilfield Sows,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3,6.5,5.3.8.E.,,, -,Oil Field Equipment,Oil and Gas - Service Rigs,Prohibited,None,,,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,10,6.5,5.3.8.F.,,, -,Oil Field Equipment,Oil and Gas - Service Rigs,Mandatory,Pony Trailers,,,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,10,6.5,5.3.8.F.i),,, -x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",Prohibited,None,,,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,10,6.5,5.3.8.F.,,, -x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",Mandatory,Pony Trailers,,,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,10,6.5,5.3.8.F.i),,, -,Oil Field Equipment,Truck Tractors,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, -X,Park Model Homes,Trucks,Mandatory,Dollies,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -X,Park Model Homes,Trucks,Mandatory,Float Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -X,Park Model Homes,Trucks,Mandatory,Pony Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3,6.5,4.2.5.A.ii)/4.2.7.A.14,,, -,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3,6.5,4.2.5.A.ii),,, -,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3,6.5,4.2.7.B.H,,, -X,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3,6.5,4.2.7.B.H,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Logging,,,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Platform Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.2,4.3,26,3.2,4.3,26,3.2,5.33,26,3.2,4.3,26,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,5.33,40,3.2,4.3,40,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,3.2,4.3,36,3.2,4.3,36,3.2,5.33,36,3.2,4.3,36,,,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers,,,,4.3,25,,4.3,25,,5.33,25,,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers - Stinger Steered Automobile Transporters,,,,4.3,25,,4.3,25,,5.33,25,,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Mandatory,Dollies,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Mandatory,Full Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Prohibited,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Mandatory,Pony Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Scraper on Dollies,Truck Tractors,Mandatory,Dollies,X,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,,,5.3.10.B.,,, -,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Truck Tractors,Prohibited,None,,,2.9,4.3,,2.9,4.3,,2.9,4.3,,2.9,4.3,,,5,4.2.7.A.6/4.5.6.C.,,, -,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Trucks,Prohibited,None,,,2.9,4.3,,2.9,4.3,,2.9,4.3,,2.9,4.3,,,5,4.2.7.A.6/4.5.6.C.,,, -,Tow Trucks And Disabled Vehicles,Tow Vehicles,Prohibited,None,,,,4.3,27.5,,4.3,27.5,,4.3,27.5,,4.3,27.5,,,5.3.14,,, -,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Truck Tractors,Prohibited,None,,,,4.3,13.5,,4.3,13.5,,4.3,13.5,,4.3,13.5,,5,4.2.7.A.6/4.5.6.D.,,, -,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Trucks,Prohibited,None,,,,4.3,13.5,,4.3,13.5,,4.3,13.5,,4.3,13.5,,5,4.2.7.A.6/4.5.6.D.,,, -,"Wood Chips, Residuals",Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,,4.45,27.5,,4.45,27.5,,4.45,27.5,,4.45,27.5,,,4.2.7.A.16/4.5.4,,, diff --git a/policy-engine/src/_examples/os-dimensions-simplified.csv b/policy-engine/src/_examples/os-dimensions-simplified.csv deleted file mode 100644 index 67f29e884..000000000 --- a/policy-engine/src/_examples/os-dimensions-simplified.csv +++ /dev/null @@ -1,212 +0,0 @@ -,"Auto Carrier, Campers And Boats (Stinger Steered Transporters Only)",Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers - Stinger Steered Automobile Transporters,,,2.6,4.3,25,2.6,4.3,25,2.6,4.88,25,2.6,4.4,25,1,1.2,4.2.7.A.1/4.2.7.B.A/ 5.3.11,,"Loaded dimensions used, equipment itself is less",FP and RP points can vary in CTPM (i.e. rear projection can be from turn centre or bumper or sometimes bunk!) -,Bridge Beams,Truck Tractors,Mandatory,Pole Trailers,X,,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,3,6.5,4.5.5/5.3.12.F.(over 31m use Non-Reducible with Semi-Trailers - Steering Trailers),,"OAL could be greater with steering trailers, do we need two categories?", -,Brushcutters (Peace Only),Truck Tractors,Mandatory,Semi-Trailers,,,2.6,4.15,23,2.6,4.15,23,4.57,5.33,23,2.6,4.15,23,3,6.5,4.2.7.B.B,,tractor semi-trailer could be 23.5 if a tridem , -,Brushcutters (Peace Only),Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,2.6,4.15,23,2.6,4.15,23,4.57,5.33,23,2.6,4.15,23,3,6.5,4.2.7.B.B,,, -,Brushcutters (Peace Only),Trucks,Mandatory,Full Trailers,,,2.6,4.15,23,2.6,4.15,23,4.57,5.33,23,2.6,4.15,23,1,1,4.2.7.B.B,,, -,Brushcutters (Peace Only),Trucks,Prohibited,None,,,2.6,4.15,12.5,2.6,4.15,12.5,4.57,5.33,12.5,2.6,4.15,12.5,1,1,4.2.7.B.B,,, -,Brushcutters (Peace Only),Trucks,Mandatory,Pony Trailers,,,2.6,4.15,23,2.6,4.15,23,3.8,5.33,23,2.6,4.15,23,1,1,4.2.7.B.B/5.3.12.G,,, -,Doglogger/Sjostrum Trailers (decked),Truck Tractors,Mandatory,None,,,2.6,4.15,13.5,2.6,4.15,13.5,2.6,4.15,13.5,2.6,4.15,13.5,1,5,4.2.7.A.13,,, -,Empty,Truck Tractors,Mandatory,Logging Trailer - Overwidth Bunks,,,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,0,0,5.3.5.B,,, -,Empty,Picker Truck Tractors,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.2,4.3,23,3.2,4.3,23,3.2,4.3,23,3.2,4.3,23,3,6.5,5.3.8.B.,,, -X,Empty,Picker Truck Tractors,Mandatory,Platform Trailers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,2.6,4.4,25,2.6,4.4,25,2.6,5.33,27.5,2.6,4.4,25,3,6.5,5.3.9.A./5.3.9.B.,,, -X,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -X,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -,Empty,Picker Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -X,Empty,Truck Tractors,Mandatory,Platform Trailers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -,Empty,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,2.6,4.4,31,2.6,4.4,31,2.6,5.33,31,2.6,4.4,31,3,6.5,5.3.12.C.,,, -,Empty,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,2.6,4.4,27.5,2.6,4.4,27.5,2.6,5.33,27.5,2.6,4.4,27.5,3,6.5,5.3.12.B.,,, -,Empty,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.4,31,3.2,4.4,31,3.2,5.33,31,3.2,4.4,31,3,6.5,5.3.12.A.,,, -X,Empty,Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -X,Empty,Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -,Empty,Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,3.2,4.88,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,3,6.5,,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,4,9.5,5.3.4.F.,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.D.,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,31,4.26,4.72,31,4.26,5.33,31,4.26,4.72,31,3,6.5,5.3.4.E.,,, -,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.A.,,, -X,Fixed Equipment,Truck Tractors,Mandatory,Fixed Equipment - Wheeler Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.B.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,4,9.5,5.3.4.F.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Counter Flow Asphalt Drum Mixers,,,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,5.3.4.D.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,1,4,5.3.4.C.,,, -,Fixed Equipment,Trucks,Mandatory,Fixed Equipment - Portable Asphalt Baghouses,,,4.26,4.72,31,4.26,4.72,31,4.26,4.72,31,4.26,4.72,31,3,6.5,5.3.4.E.,,, -,Garbage Bins,Trucks,Mandatory,Full Trailers,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,3,6.5,4.2.7.A.7,,, -,Garbage Bins,Trucks,Prohibited,None,,,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,3,6.5,4.2.7.A.7,,, -,Garbage Bins,Trucks,Mandatory,Pony Trailers,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,3,6.5,4.2.7.A.7,,, -,"Grader, Tractor Blades, Buckets",Fixed Equipment - Trucks/Graders etc.,Prohibited,None,,,4.4,4.15,12.5,4.4,4.15,12.5,4.4,4.15,12.5,4.4,4.15,12.5,1,1,4.2.7.B.C,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,1,1,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.5,4.3,27.5,3.5,4.3,27.5,3.84,4.8,27.5,3.5,4.3,27.5,1,1,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Trucks,Mandatory,Full Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Trucks,Prohibited,None,,,3.5,4.3,12.5,3.5,4.3,12.5,3.84,4.8,12.5,3.5,4.3,12.5,1,1,4.2.7.A.8/4.4.2,,, -,Hay Bales (Round) Peace River Only,Trucks,Mandatory,Pony Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,1,1,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,1,1,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.4,23,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Trucks,Mandatory,Full Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Trucks,Prohibited,None,,,3.05,4.3,12.5,3.05,4.3,12.5,3.05,4.8,12.5,3.05,4.3,12.5,1,1,4.2.7.A.8/4.4.1,,, -,Hay Bales Large Rectangular,Trucks,Mandatory,Pony Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, -,Hay Bales Round,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,1,1,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.5,4.3,27.5,3.5,4.3,27.5,3.84,4.8,27.5,3.5,4.3,27.5,1,1,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,3,6.5,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Trucks,Mandatory,Full Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Trucks,Prohibited,None,,,3.5,4.3,12.5,3.5,4.3,12.5,3.84,4.8,12.5,3.5,4.3,12.5,1,1,4.2.7.A.8/4.4.2,,, -,Hay Bales Round,Trucks,Mandatory,Pony Trailers,,,3.5,4.3,23,3.5,4.3,23,3.84,4.8,23,3.5,4.3,23,1,4,4.2.7.A.8/4.4.2,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,1,1,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,1,1,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,3,6.5,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Trucks,Mandatory,Full Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Trucks,Prohibited,None,,,3.05,4.3,12.5,3.05,4.3,12.5,3.05,4.8,12.5,3.05,4.3,12.5,1,1,4.2.7.A.8/4.4.1,,, -,Hay Bales Small Rectangular,Trucks,Mandatory,Pony Trailers,,,3.05,4.3,23,3.05,4.3,23,3.05,4.8,23,3.05,4.3,23,1,4,4.2.7.A.8/4.4.1,,, -,Hayrack Semi-Trailer with a Folded Chassis/Empty Piggyback,Truck Tractors,Mandatory,Semi-Trailers - Logging,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,1,5,4.5.6.E.,,, -,Intermodal Containers,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,2.6,4.4,26,2.6,4.4,26,2.6,4.4,26,2.6,4.4,26,1,1,4.2.7.A.9,,, -,Intermodal Containers,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,2.6,4.4,27.5,2.6,4.4,27.5,2.6,4.4,27.5,2.6,4.4,27.5,1,1,4.2.7.A.9,,, -,Intermodal Containers,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,2.6,4.4,23,2.6,4.4,23,2.6,4.4,23,2.6,4.4,23,1,1,4.2.7.A.9,,, -,Intermodal Containers,Trucks,Mandatory,None,,,2.6,4.4,12.5,2.6,4.4,12.5,2.6,4.4,12.5,2.6,4.4,12.5,1,1,4.2.7.A.9,,, -,Intermodal Containers without Sides,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,4.4,4.72,23,4.4,4.72,23,4.4,4.72,23,4.4,4.72,23,1,1,4.2.7.10/4.2.7.B.D.,,, -,Laminated Beams,Truck Tractors,Mandatory,Pole Trailers,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,3,6.5,4.5.3,,, -,Laminated Beams,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,2.6,4.15,31,3,6.5,4.5.3,,, -X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,"Logging Trailer - Full Trailers, Tri Axle, Quad Axle",,,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,10,10,4.5.2,,, -X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,Pole Trailers,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,8,9,4.5.2,,, -,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,10,10,5.3.12.F,,, -,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,10,10,5.3.12.F,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Full Trailers,,,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Legacy Logging Trailer Combinations,,,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,3,5,5.3.7.C.,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Pole Trailers,X,,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,2.9,4.15,26,2.9,4.15,26,2.9,4.15,26,2.9,4.15,26,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,2.9,4.15,27.5,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,,,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,3,6.5,4.5.1,,, -,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Mandatory,Semi-Trailers - Logging,,,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,2.9,4.15,25,3,6.5,4.5.1,,, -,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,Mandatory,Semi-Trailers,X,X,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,3.0,7.5,4.3.5,,, -,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,3.0,7.5,4.3.5,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Mandatory,Dollies,,,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,1,6.9,4.3,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Mandatory,Float Trailers,,,5.0,4.57,31.5,5.0,4.57,31.5,5.0,5.33,36,5.0,4.57,31.5,1,6.9,4.3.5.F,,, -,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Mandatory,Pony Trailers,,,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,1,6.9,4.3,,, -,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,Mandatory,Semi-Trailers,X,X,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,3.0,7.5,4.3.5,,, -,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,3.0,7.5,4.3.5,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Mandatory,Dollies,,,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,1,6.9,4.3,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Mandatory,Float Trailers,,,6.0,4.57,31.5,6.0,4.57,31.5,6.1,5.33,36,6.0,4.57,31.5,1,6.9,4.3.5.F,,, -X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Mandatory,Pony Trailers,,,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,1,6.9,4.3,,, -,None,Concrete Pumper Trucks,Prohibited,None,,X,2.6,4.15,15.5,2.6,4.15,15.5,2.6,4.15,15.5,2.6,4.15,15.5,3,6.5,5.3.2.A.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",Prohibited,Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.B.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",Prohibited,None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.B.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",Prohibited,Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.A.,,, -,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",Prohibited,None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.A.,,, -,None,Double Decker Buses,Prohibited,None,,,2.6,4.3,12.5,2.6,4.3,12.5,2.6,4.3,12.5,2.6,4.42,12.5,1,1,5.3.1.A,,, -,None,Fixed Equipment - Trucks/Graders etc.,Prohibited,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,1,4,,,, -X,None,Fixed Equipment - Trucks/Graders etc.,Prohibited,None,,,3.5,4.3,12.5,3.5,4.3,12.5,3.5,5.33,12.5,3.5,4.4,12.5,1,1,5.3.4.G.,,, -X,None,Inter-City Bus (Pulling Pony Trailer),Mandatory,Pony Trailers,,,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,1,1,5.3.1.B,,, -,None,Logging Trucks - Off-Highway,Mandatory,Semi-Trailers - Logging,,,4.4,4.15,23,4.4,4.15,23,4.4,4.15,23,4.4,4.15,23,3,6.5,5.3.5.A.,,, -X,None,Long Combination Vehicles (LCV) - Rocky Mountain Doubles,Prohibited,Semi-Trailers,,,2.6,4.15,32,2.6,4.15,32,2.6,4.15,31,2.6,4.15,32,1,1,5.3.6.D.,,, -,None,Long Combination Vehicles (LCV) - Turnpike Doubles,Prohibited,Semi-Trailers,,,2.6,4.15,41,2.6,4.15,41,2.6,4.15,41,2.6,4.15,41,1,1,5.3.6.D.,,, -X,None,Long Wheelbase Truck Tractors Exceeding 6.2 m up to 7.25 m,Mandatory,Semi-Trailers,X,X,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,1,1,5.3.7.A.,,, -,None,Picker Truck Tractors,Prohibited,None,,,2.6,4.15,16,2.6,4.15,16,2.6,4.15,16,2.6,4.15,16,3,1,5.3.9,,, -,None,Picker Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,2.6,4.15,25,3,6.5,5.3.9,,, -,None,Picker Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,3,6.5,,,, -,None,Picker Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,3,6.5,,,, -,None,Scrapers,Prohibited,None,,,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,2.6,4.15,12.5,1,1,5.3.10.A.1),,, -,None,Truck Tractors,Mandatory,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,4,9.5,,,, -,None,Truck Tractors,Mandatory,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,,,, -X,None,Truck Tractors,Mandatory,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,31,4.26,4.72,31,4.26,5.33,31,4.26,4.72,31,3,6.5,,,, -X,None,Truck Tractors,Mandatory,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5,,,, -,None,Truck Tractors,Mandatory,Fixed Equipment - Wheeler Semi-Trailers,X,X,5,4.88,31,5,4.88,31,5,5.33,31,5,4.88,31,3,6.5,,,, -X,None,Truck Tractors,Mandatory,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,1,1,5.3.12.D.,,, -,None,Truck Tractors,Mandatory,Ready Mix Concrete Pump Semi-Trailers,X,X,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,3,6.5,5.3.2.B.,,, -,None,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,2.6,4.3,23,2.6,4.3,23,2.6,5.33,31,2.6,4.3,31,3,6.5,5.3.12.B.,,, -,None,Truck Tractors,Mandatory,Semi-Trailers - Insulated Vans with Reefer/Refrigeration Units,,,2.6,4.3,23,2.6,4.3,23,2.6,4.3,23,2.6,4.3,23,1,1,5.3.12.H.,,, -,None,Truck Tractors,Mandatory,Semi-Trailers - Non-Tac Short Chassis,,,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,2.6,4.15,20,5,2.25,5.3.7.D.,,, -,None,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,32,3.2,4.3,27.5,3,6.5,5.3.12.A.,,, -,None,Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,2.6,4.15,40,1,1,5.3.12.F.ii),,, -,None,Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,2.6,4.15,36,1,1,5.3.12.F.i),,, -,None,Trucks,Mandatory,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,4,9.5,,,, -,None,Trucks,Mandatory,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,1,4,,,, -,None,Trucks,Mandatory,Full Trailers ,,,3.8,4.3,23,3.8,4.3,23,3.8,5.33,23,3.8,4.3,23,1,6.5,5.3.12.G.,,, -,None,Trucks,Mandatory,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW) with Attached Axles",,,5.0,4.88,31.5,5.0,4.88,31.5,5.0,5.33,36,5.0,4.88,31.5,1,6.9,"4.3.5.A, 4.3.5.B, 4.3.5.C",,, -,None,Trucks,Mandatory,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW) with Attached Axles",,,6.0,4.88,31.5,6.0,4.88,31.5,6.1,5.33,36,6.0,4.88,31.5,1,6.9,"4.3.5.D, 4.3.5.E",,, -,None,Trucks,Mandatory,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5,,,, -,None,Trucks,Mandatory,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,3.65,4.15,23,1,1,5.3.12.D.,,, -,None,Trucks,Mandatory,Park Model Homes with Attached Axles,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -,None,Trucks,Mandatory,Pony Trailers,,,3.2,4.3,23,3.2,4.3,23,3.2,5.33,23,3.2,4.3,23,1,6.5,5.3.12.G.,,, -,None,Trucks Equipped with Front or Underbody Plow Blades,Prohibited,None,,,3.2,4.15,12.5,3.2,4.15,12.5,3.2,4.15,12.5,3.2,4.15,12.5,1,1,5.3.13,,, -X,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Logging,,,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,3,6.5,5.3.9.A./5.3.9.B.,,, -X,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers,X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,6.5,5.3.9.A./5.3.9.B.,non compliant vehicle,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,6.5,5.3.9.A./5.3.9.B.,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,6.5,5.3.9.A./5.3.9.B.,,, -X,Non-Reducible Loads,Picker Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,6.5,5.3.9.A./5.3.9.B.,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, -X,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,3,6.5,,,, -,Non-Reducible Loads,Picker Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,3,6.5,,,, -X,Non-Reducible Loads,Truck Tractors,Mandatory,Platform Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,5,4.4,26,5,4.4,26,5,5.33,26,5,4.4,26,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Non-Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Non-Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers,,,2.6,4.88,25,2.6,4.88,25,2.6,5.33,25,2.6,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, -,Non-Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers - Stinger Steered Automobile Transporters,,,2.6,4.88,25,2.6,4.88,25,2.6,5.33,25,2.6,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, -,Non-Reducible Loads,Trucks,Mandatory,Dollies,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.A.2/4.2.7.B.B/ 5.3.11,,, -,Non-Reducible Loads,Trucks,Mandatory,Full Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G,,, -,Non-Reducible Loads,Trucks,Prohibited,None,,,5,4.88,16,5,4.88,16,5,5.33,16,5,4.88,16,1,6.5,4.2.7.B.G,,, -,Non-Reducible Loads,Trucks,Mandatory,Pony Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G,,, -,Oil Field Equipment,Oil and Gas - Bed Trucks,Mandatory,Expando Semi-Trailers,X,X,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,3,6.5,5.3.8.C.,,, -,Oil Field Equipment,Oil and Gas - Bed Trucks,Prohibited,None,,,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,1,5,5.3.8.A.i)/ii),,, -,Oil Field Equipment,Oil and Gas - Bed Trucks,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, -,Oil Field Equipment,Oil and Gas - Oilfield Sows,Prohibited,None,,,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,1,5,5.3.8.D.,,, -,Oil Field Equipment,Oil and Gas - Oilfield Sows,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3,6.5,5.3.8.E.,,, -,Oil Field Equipment,Oil and Gas - Service Rigs,Prohibited,None,,,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,10,6.5,5.3.8.F.,,, -,Oil Field Equipment,Oil and Gas - Service Rigs,Mandatory,Pony Trailers,,,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,10,6.5,5.3.8.F.i),,, -x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",Prohibited,None,,,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,10,6.5,5.3.8.F.,,, -x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",Mandatory,Pony Trailers,,,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,10,6.5,5.3.8.F.i),,, -,Oil Field Equipment,Truck Tractors,Mandatory,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B.,,, -,Park Model Homes,Trucks,Mandatory,Dollies,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -,Park Model Homes,Trucks,Mandatory,Float Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -,Park Model Homes,Trucks,Mandatory,Pony Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4,,, -,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3,6.5,4.2.5.A.ii)/4.2.7.A.14,,, -,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3,6.5,4.2.5.A.ii),,, -,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3,6.5,4.2.7.B.H,,, -X,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3,6.5,4.2.7.B.H,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Logging,,,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Picker Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Platform Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - A-Trains and C-Trains,,,3.2,4.3,26,3.2,4.3,26,3.2,5.33,26,3.2,4.3,26,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers - Wide Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Semi-Trailers with Crane,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,5.33,40,3.2,4.3,40,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors,Mandatory,Steering Trailers - Self/Remote,X,X,3.2,4.3,36,3.2,4.3,36,3.2,5.33,36,3.2,4.3,36,3,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers,,,2.6,4.3,25,2.6,4.3,25,2.6,5.33,25,2.6,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Truck Tractors - Stinger Steered,Mandatory,Semi-Trailers - Stinger Steered Automobile Transporters,,,2.6,4.3,25,2.6,4.3,25,2.6,5.33,25,2.6,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Mandatory,Dollies,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Mandatory,Full Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Prohibited,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -,Reducible Loads,Trucks,Mandatory,Pony Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6,,, -X,Scraper on Dollies,Truck Tractors,Mandatory,Dollies,X,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,1,5.3.10.B.,,, -,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Truck Tractors,Prohibited,None,,,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,1,5,4.2.7.A.6/4.5.6.C.,,, -,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Trucks,Prohibited,None,,,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,2.9,4.3,12.5,1,5,4.2.7.A.6/4.5.6.C.,,, -,Tow Trucks And Disabled Vehicles,Tow Vehicles,Prohibited,None,,,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,2.6,4.3,27.5,1,1,5.3.14,,, -,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Truck Tractors,Prohibited,None,,,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,1,5,4.2.7.A.6/4.5.6.D.,,, -,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Trucks,Prohibited,None,,,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,2.6,4.3,13.5,1,5,4.2.7.A.6/4.5.6.D.,,, -,"Wood Chips, Residuals",Truck Tractors,Mandatory,Semi-Trailers - B-Trains,,,2.6,4.45,27.5,2.6,4.45,27.5,2.6,4.45,27.5,2.6,4.45,27.5,1,1,4.2.7.A.16/4.5.4,,, diff --git a/policy-engine/src/_examples/os-dimensions.csv b/policy-engine/src/_examples/os-dimensions.csv new file mode 100644 index 000000000..5472487ef --- /dev/null +++ b/policy-engine/src/_examples/os-dimensions.csv @@ -0,0 +1,213 @@ +No Self Issue,Commodity,Power Unit,Trailer,Allow Jeep,Allow Booster,LMN - Width,LMN - Height,LMN - Length,KTN - Width,KTN - Height,KTN - Length,PCE - Width,PCE - Height,PCE - Length,BCD - Width,BCD - Height,BCD - Length,ORBC FP,ORBC RP,"Policy Ref" +,"Auto Carrier, Campers And Boats (Stinger Steered Transporters Only)",Truck Tractors - Stinger Steered,Semi-Trailers - Stinger Steered Automobile Transporters,,,,4.3,25,,4.3,25,,4.88,25,,4.4,25,1,1.2,4.2.7.A.1/4.2.7.B.A/ 5.3.11 +,Bridge Beams,Truck Tractors,Pole Trailers,X,,,,31,,,31,,,31,,,31,3,6.5,4.5.5 +,Bridge Beams,Truck Tractors,Semi-Trailers,,,,,31,,,31,,,31,,,31,3,6.5,4.2.7.A.13 +,Bridge Beams,Truck Tractors,Steering Dolly,,,,,36,,,36,,,36,,,36,3,6.5,5.3.12.F +,Brushcutters (Peace Only),Truck Tractors,Semi-Trailers,,,,,,,,,4.57,5.33,,,,,3,6.5,4.2.7.B.B +,Brushcutters (Peace Only),Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,,,,,,,4.57,5.33,,,,,3,6.5,4.2.7.B.B +,Brushcutters (Peace Only),Trucks,None,,,,,,,,,4.57,5.33,,,,,,,4.2.7.B.B +,Brushcutters (Peace Only),Trucks,Pony Trailers,,,,,,,,,3.8,5.33,,,,,,,4.2.7.B.B/5.3.12.G +,Doglogger/Sjostrum Trailers (decked),Truck Tractors,None,,,,,13.5,,,13.5,,,13.5,,,13.5,,5,4.2.7.A.13 +,Empty,Truck Tractors,Logging Trailer - Overwidth Bunks,,,3.2,,,3.2,,,3.2,,,3.2,,,0,0,5.3.5.B +,Empty,Picker Truck Tractors,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,3.2,4.15,23,3,6.5,5.3.8.B. +X,Empty,Picker Truck Tractors,Platform Trailers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,,, +,Empty,Picker Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,,4.15,,,4.15,,,4.15,27.5,,4.15,25,3,6.5,5.3.9.A./5.3.9.B. +X,Empty,Picker Truck Tractors,Semi-Trailers - Wheelers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,,, +X,Empty,Picker Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,,, +,Empty,Picker Truck,Pony Trailers,,,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,2.6,4.15,23,,,5.3.9.A +X,Empty,Truck Tractors,Platform Trailers,X,X,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,,, +,Empty,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,,4.15,31,,,31,,,31,,,31,3,6.5,5.3.12.C. +,Empty,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,,4.15,27.5,,,27.5,,,27.5,,,27.5,3,6.5,5.3.12.B. +,Empty,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.15,31,3.2,,31,3.2,,31,3.2,,31,3,6.5,5.3.12.A. +X,Empty,Truck Tractors,Semi-Trailers - Wheelers,X,X,3.2,4.15,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,, +X,Empty,Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,3.2,4.15,27.5,3.2,4.88,27.5,3.2,5.33,27.5,3.2,4.88,27.5,,, +,Fixed Equipment,Truck Tractors,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,4,9.5,5.3.4.F. +,Fixed Equipment,Truck Tractors,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.D. +,Fixed Equipment,Truck Tractors,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,,4.26,4.72,,4.26,5.33,,4.26,4.72,,3,6.5,5.3.4.E. +,Fixed Equipment,Truck Tractors,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.A. +X,Fixed Equipment,Truck Tractors,Fixed Equipment - Wheeler Semi-Trailers,X,X,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.B. +,Fixed Equipment,Trucks,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,,3.2,4.3,,3.2,4.3,,3.2,4.3,,4,9.5,5.3.4.F. +,Fixed Equipment,Trucks,Fixed Equipment - Counter Flow Asphalt Drum Mixers,,,3.8,4.72,,3.8,4.72,,3.8,5.33,,3.8,4.72,,3,6.5,5.3.4.D. +,Fixed Equipment,Trucks,Fixed Equipment - Pony Trailers,,,3.2,4.3,,3.2,4.3,,3.2,4.3,,3.2,4.3,,,4,5.3.4.C. +,Fixed Equipment,Trucks,Fixed Equipment - Portable Asphalt Baghouses,,,4.26,4.72,,4.26,4.72,,4.26,4.72,,4.26,4.72,,3,6.5,5.3.4.E. +,Garbage Bins,Trucks,Full Trailers,,,,,,,,,,,,,,,3,6.5,4.2.7.A.7 +,Garbage Bins,Trucks,None,,,,,,,,,,,,,,,3,6.5,4.2.7.A.7 +,Garbage Bins,Trucks,Pony Trailers,,,,,,,,,,,,,,,3,6.5,4.2.7.A.7 +,"Grader, Tractor Blades, Buckets",Fixed Equipment - Trucks/Graders etc.,None,,,4.4,,,4.4,,,4.4,,,4.4,,,,,4.2.7.B.C +,Hay Bales (Round) Peace River Only,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,,,4.2.7.A.8/4.4.2 +,Hay Bales (Round) Peace River Only,Truck Tractors,Semi-Trailers - B-Trains,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,,4.2.7.A.8/4.4.2 +,Hay Bales (Round) Peace River Only,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2 +,Hay Bales (Round) Peace River Only,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2 +,Hay Bales (Round) Peace River Only,Trucks,Full Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2 +,Hay Bales (Round) Peace River Only,Trucks,None,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,,4.2.7.A.8/4.4.2 +,Hay Bales (Round) Peace River Only,Trucks,Pony Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2 +,Hay Bales Large Rectangular,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,,,4.2.7.A.8/4.4.1 +,Hay Bales Large Rectangular,Truck Tractors,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,,,4.2.7.A.8/4.4.1 +,Hay Bales Large Rectangular,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.4,,3,6.5,4.2.7.A.8/4.4.1 +,Hay Bales Large Rectangular,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,3,6.5,4.2.7.A.8/4.4.1 +,Hay Bales Large Rectangular,Trucks,Full Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1 +,Hay Bales Large Rectangular,Trucks,None,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,,4.2.7.A.8/4.4.1 +,Hay Bales Large Rectangular,Trucks,Pony Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1 +,Hay Bales Round,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.5,4.3,26,3.5,4.3,26,3.84,4.8,26,3.5,4.3,26,,,4.2.7.A.8/4.4.2 +,Hay Bales Round,Truck Tractors,Semi-Trailers - B-Trains,,,3.5,4.3,27.5,3.5,4.3,27.5,3.84,4.8,27.5,3.5,4.3,27.5,,,4.2.7.A.8/4.4.2 +,Hay Bales Round,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2 +,Hay Bales Round,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,3,6.5,4.2.7.A.8/4.4.2 +,Hay Bales Round,Trucks,Full Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2 +,Hay Bales Round,Trucks,None,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,,4.2.7.A.8/4.4.2 +,Hay Bales Round,Trucks,Pony Trailers,,,3.5,4.3,,3.5,4.3,,3.84,4.8,,3.5,4.3,,,4,4.2.7.A.8/4.4.2 +,Hay Bales Small Rectangular,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.05,4.3,26,3.05,4.3,26,3.05,4.8,26,3.05,4.3,26,,,4.2.7.A.8/4.4.1 +,Hay Bales Small Rectangular,Truck Tractors,Semi-Trailers - B-Trains,,,3.05,4.3,27.5,3.05,4.3,27.5,3.05,4.8,27.5,3.05,4.3,27.5,,,4.2.7.A.8/4.4.1 +,Hay Bales Small Rectangular,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,3,6.5,4.2.7.A.8/4.4.1 +,Hay Bales Small Rectangular,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,3,6.5,4.2.7.A.8/4.4.1 +,Hay Bales Small Rectangular,Trucks,Full Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1 +,Hay Bales Small Rectangular,Trucks,None,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,,4.2.7.A.8/4.4.1 +,Hay Bales Small Rectangular,Trucks,Pony Trailers,,,3.05,4.3,,3.05,4.3,,3.05,4.8,,3.05,4.3,,,4,4.2.7.A.8/4.4.1 +,Hayrack Semi-Trailer with a Folded Chassis/Empty Piggyback,Truck Tractors,Semi-Trailers - Logging,,,,,,,,,,,,,,,,5,4.5.6.E. +,Intermodal Containers,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,,4.4,26,,4.4,26,,4.4,26,,4.4,26,,,4.2.7.A.9 +,Intermodal Containers,Truck Tractors,Semi-Trailers - B-Trains,,,,4.4,27.5,,4.4,27.5,,4.4,27.5,,4.4,27.5,,,4.2.7.A.9 +,Intermodal Containers,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,,4.4,,,4.4,,,4.4,,,4.4,,,,4.2.7.A.9 +,Intermodal Containers,Trucks,None,,,,4.4,,,4.4,,,4.4,,,4.4,,,,4.2.7.A.9 +,Intermodal Containers without Sides,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,4.4,4.72,,4.4,4.72,,4.4,4.72,,4.4,4.72,,,,4.2.7.10/4.2.7.B.D. +,Laminated Beams,Truck Tractors,Pole Trailers,X,X,,,40,,,40,,,40,,,40,3,6.5,4.5.3 +,Laminated Beams,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,,,31,,,31,,,31,,,31,3,6.5,4.5.3 +X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,"Logging Trailer - Full Trailers, Tri Axle, Quad Axle",,,,,40,,,40,,,40,,,40,10,10,4.5.2 +X,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Pole Trailers,X,X,,,40,,,40,,,40,,,40,8,9,4.5.2 +,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Steering Trailers - Manned,X,X,,,40,,,40,,,40,,,40,10,10,5.3.12.F +,"Logs, Poles And Boomsticks (Over 20.1)",Truck Tractors,Steering Trailers - Self/Remote,X,X,,,36,,,36,,,36,,,36,10,10,5.3.12.F +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Full Trailers,,,2.9,,27.5,2.9,,27.5,2.9,,27.5,2.9,,27.5,3,6.5,4.5.1 +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Legacy Logging Trailer Combinations,,,2.6,,25,2.6,,25,2.6,,25,2.6,,25,3,5,5.3.7.C. +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Pole Trailers,X,,2.9,,27.5,2.9,,27.5,2.9,,27.5,2.9,,27.5,3,6.5,4.5.1 +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,2.9,,26,2.9,,26,2.9,,26,2.9,,26,3,6.5,4.5.1 +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - B-Trains,,,2.9,,27.5,2.9,,27.5,2.9,,27.5,2.9,,27.5,3,6.5,4.5.1 +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,,,2.9,,25,2.9,,25,2.9,,25,2.9,,25,3,6.5,4.5.1 +,"Logs, Poles And Boomsticks (Up To 20.1)",Truck Tractors,Semi-Trailers - Logging,,,2.9,,25,2.9,,25,2.9,,25,2.9,,25,3,6.5,4.5.1 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,Semi-Trailers,X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Dollies,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Float Trailers,,,,4.57,,,4.57,,,5.33,36,,4.57,31.5,1,6.9,4.3.5.F +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)",Trucks,Pony Trailers,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,Semi-Trailers,X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,,4.88,,,4.88,,,5.33,36,,4.88,31.5,3.0,7.5,4.3.5 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Dollies,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3 +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Float Trailers,,,,4.57,,,4.57,,,5.33,36,,4.57,31.5,1,6.9,4.3.5.F +X,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)",Trucks,Pony Trailers,,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,4.3 +,None,Concrete Pumper Trucks,None,,X,,,15.5,,,15.5,,,15.5,,,15.5,3,6.5,5.3.2.A. +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.B. +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - All Terrain",None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.B. +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",Dollies,,,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,3.8,4.3,25,10,6.5,5.3.3.A. +,None,"Cranes, Rubber-Tired Loaders, Firetrucks - Mobile",None,,,3.8,4.3,14,3.8,4.3,14,3.8,4.3,15,3.8,4.3,14,10,6.5,5.3.3.A. +,None,Double Decker Buses,None,,,,4.3,,,4.3,,,4.3,,,4.42,,,,5.3.1.A +,None,Fixed Equipment - Trucks/Graders etc.,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,,4, +X,None,Fixed Equipment - Trucks/Graders etc.,None,,,3.5,4.3,,3.5,4.3,,3.5,5.33,,3.5,4.4,12.5,,,5.3.4.G. +X,None,Inter-City Bus (Pulling Pony Trailer),Pony Trailers,,,,,,,,,,,,,,,,,5.3.1.B +,None,Logging Trucks - Off-Highway,Semi-Trailers - Logging,,,4.4,,,4.4,,,4.4,,,4.4,,,3,6.5,5.3.5.A. +X,None,Long Combination Vehicles (LCV) - Rocky Mountain Doubles,Semi-Trailers,,,,,32,,,32,,,31,,,32,,,5.3.6.D. +,None,Long Combination Vehicles (LCV) - Turnpike Doubles,Semi-Trailers,,,,,41,,,41,,,41,,,41,,,5.3.6.D. +X,None,Long Wheelbase Truck Tractors Exceeding 6.2 m up to 7.25 m,Semi-Trailers,X,X,,,,,,,,,,2.6,4.15,23,,,5.3.7.A. +,None,Picker Truck Tractors,None,,,,,16,,,16,,,16,2.6,4.15,16,3,,5.3.9 +,None,Picker Truck Tractors,Semi-Trailers with Crane,X,X,,,25,,,25,,,25,2.6,4.15,25,3,6.5,5.3.9 +,None,Picker Truck Tractors,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,,, +,None,Picker Truck Tractors,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,,, +,None,Scrapers,None,,,,,,,,,,,,,,12.5,,,5.3.10.A.1) +,None,Truck Tractors,Fixed Equipment - Conveyors (Semi-Trailers),X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,4,9.5, +,None,Truck Tractors,Fixed Equipment - Counter Flow Asphalt Drum Mixers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5, +X,None,Truck Tractors,Fixed Equipment - Portable Asphalt Baghouses,X,X,4.26,4.72,31,4.26,4.72,31,4.26,5.33,31,4.26,4.72,31,3,6.5, +X,None,Truck Tractors,Fixed Equipment - Semi-Trailers,X,X,3.8,4.72,31,3.8,4.72,31,3.8,5.33,31,3.8,4.72,31,3,6.5, +,None,Truck Tractors,Fixed Equipment - Wheeler Semi-Trailers,X,X,5,4.88,31,5,4.88,31,5,5.33,31,5,4.88,31,,, +X,None,Truck Tractors,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,,,3.65,,,3.65,,,3.65,,,,,5.3.12.D. +,None,Truck Tractors,Ready Mix Concrete Pump Semi-Trailers,X,X,,,,,,,,,,,,,3,6.5,5.3.2.B. +,None,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,,4.3,,,4.3,,,5.33,,,4.3,31,3,6.5,5.3.12.B. +,None,Truck Tractors,Semi-Trailers - Insulated Vans with Reefer/Refrigeration Units,,,,4.3,,,4.3,,,4.3,,,4.3,,,,5.3.12.H. +,None,Truck Tractors,Semi-Trailers - Non-Tac Short Chassis,,,,,,,,,,,,,,,,,5.3.7.D. +,None,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,32,3.2,4.3,27.5,3,6.5,5.3.12.A. +,None,Truck Tractors,Steering Trailers - Manned,X,X,,,40,,,40,,,40,,4.15,40,,,5.3.12.F.ii) +,None,Truck Tractors,Steering Trailers - Self/Remote,X,X,,,36,,,36,,,36,,,36,,,5.3.12.F.i) +,None,Trucks,Fixed Equipment - Conveyors (Pony Trailers),,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,4,9.5, +,None,Trucks,Fixed Equipment - Pony Trailers,,,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,,4, +,None,Trucks,Full Trailers ,,,3.8,4.3,,3.8,4.3,,3.8,5.33,,3.8,4.3,,,6.5,5.3.12.G. +,None,Trucks,"Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW) with Attached Axles",,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,"4.3.5.A, 4.3.5.B, 4.3.5.C" +,None,Trucks,"Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW) with Attached Axles",,,,4.88,,,4.88,,,5.33,36,,4.88,31.5,1,6.9,"4.3.5.D, 4.3.5.E" +,None,Trucks,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5, +,None,Trucks,Overdimensional Trailers and Semi-Trailers (For Export),,,3.65,,,3.65,,,3.65,,,3.65,,,,,5.3.12.D. +,None,Trucks,Park Model Homes with Attached Axles,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4 +,None,Trucks,Pony Trailers,,,3.2,4.3,,3.2,4.3,,3.2,5.33,,3.2,4.3,,,6.5,5.3.12.G. +,None,Trucks Equipped with Front or Underbody Plow Blades,None,,,3.2,,,3.2,,,3.2,,,3.2,,,,,5.3.13 +X,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Logging,,,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,3.8,4.15,27.5,,,5.3.9.A./5.3.9.B. +X,Non-Reducible Loads,Picker Truck Tractors,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B. +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers,X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,,5.3.9.A./5.3.9.B. +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,,5.3.9.A./5.3.9.B. +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,25,5,4.4,25,5,5.33,25,5,4.4,25,3,,5.3.9.A./5.3.9.B. +X,Non-Reducible Loads,Picker Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,3,,5.3.9.A./5.3.9.B. +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,, +X,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,, +,Non-Reducible Loads,Picker Truck Tractors,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,, +,Non-Reducible Loads,Picker Truck Tractors,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,,, +,Non-Reducible Loads,Picker Truck Tractors,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,,, +X,Non-Reducible Loads,Truck Tractors,Platform Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,5,4.4,26,5,4.4,26,5,5.33,26,5,4.4,26,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - B-Trains,,,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,5,4.4,27.5,5,4.4,27.5,5,5.33,27.5,5,4.4,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6 +X,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Steering Trailers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Semi-Trailers with Crane,X,X,5,4.88,27.5,5,4.88,27.5,5,5.33,27.5,5,4.88,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Steering Trailers - Manned,X,X,5,4.88,40,5,4.88,40,5,5.33,40,5,4.88,40,,,4.2.4/4.2.5i)/4.2.6 +,Non-Reducible Loads,Truck Tractors,Steering Trailers - Self/Remote,X,X,5,4.88,36,5,4.88,36,5,5.33,36,5,4.88,36,,,4.2.4/4.2.5i)/4.2.6 +X,Non-Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers,,,,4.88,25,,4.88,25,,5.33,25,,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11 +,Non-Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers - Stinger Steered Automobile Transporters,,,,4.88,25,,4.88,25,,5.33,25,,4.88,25,1,1.2,4.2.7.A.2/4.2.7.B.B/ 5.3.11 +,Non-Reducible Loads,Trucks,Dollies,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.A.2/4.2.7.B.B/ 5.3.11 +,Non-Reducible Loads,Trucks,Full Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G +,Non-Reducible Loads,Trucks,None,,,5,4.88,16,5,4.88,16,5,5.33,16,5,4.88,16,1,6.5,4.2.7.B.G +,Non-Reducible Loads,Trucks,Pony Trailers,,,5,4.88,25,5,4.88,25,5,5.33,25,5,4.88,25,1,6.5,4.2.7.B.G +,Oil Field Equipment,Oil and Gas - Bed Trucks,Expando Semi-Trailers,X,X,,4.3,27.5,,4.3,27.5,,4.3,27.5,,4.3,27.5,3,6.5,5.3.8.C. +,Oil Field Equipment,Oil and Gas - Bed Trucks,None,,,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,3.3,4.3,14,,5,5.3.8.A.i)/ii) +,Oil Field Equipment,Oil and Gas - Bed Trucks,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B. +,Oil Field Equipment,Oil and Gas - Oilfield Sows,None,,,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,3.2,4.3,15,,5,5.3.8.D. +,Oil Field Equipment,Oil and Gas - Oilfield Sows,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3.2,4.3,25,3,6.5,5.3.8.E. +,Oil Field Equipment,Oil and Gas - Service Rigs,None,,,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,3.8,4.3,15,10,6.5,5.3.8.F. +,Oil Field Equipment,Oil and Gas - Service Rigs,Pony Trailers,,,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,3.8,4.3,23,10,6.5,5.3.8.F.i) +x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",None,,,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,2.9,4.15,15.5,10,6.5,5.3.8.F. +x,Oil Field Equipment,"Oil and Gas - Service Rigs and Rathole Augers Only Equipped with Heavy Front Projected Crane (must exceed 14,000 kg tare weight)",Pony Trailers,,,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,2.9,4.15,23,10,6.5,5.3.8.F.i) +,Oil Field Equipment,Truck Tractors,Oil and Gas - Oversize Oilfield Flat Deck Semi-Trailers,X,X,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3.3,4.3,23,3,6.5,5.3.8.B. +,Park Model Homes,Trucks,Dollies,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4 +,Park Model Homes,Trucks,Float Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4 +,Park Model Homes,Trucks,Pony Trailers,,,4.4,4.88,31.5,4.4,4.88,31.5,4.4,5.33,31.5,4.4,4.88,31.5,3.0,6.9,4.3.4 +,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3.2,4.3,31,3,6.5,4.2.5.A.ii)/4.2.7.A.14 +,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3.2,4.3,27.5,3,6.5,4.2.5.A.ii) +,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3.2,4.3,36,3,6.5,4.2.7.B.H +X,"Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)",Truck Tractors,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3.2,4.3,40,3,6.5,4.2.7.B.H +,Reducible Loads,Truck Tractors,Semi-Trailers - Logging,,,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,3.2,4.15,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Picker Truck Tractors,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Picker Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Picker Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,3,,4.2.4/4.2.5i)/4.2.6 +X,Reducible Loads,Truck Tractors,Platform Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Semi-Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Semi-Trailers - A-Trains and C-Trains,,,3.2,4.3,26,3.2,4.3,26,3.2,5.33,26,3.2,4.3,26,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Semi-Trailers - B-Trains,,,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Expandos,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Semi-Trailers - Hiboys/Flat Decks,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,"Semi-Trailers - Single Drop, Double Drop, Step Decks, Lowbed, Expandos, etc.",X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Semi-Trailers - Steering Trailers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +X,Reducible Loads,Truck Tractors,Semi-Trailers - Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +X,Reducible Loads,Truck Tractors,Semi-Trailers - Wide Wheelers,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Semi-Trailers with Crane,X,X,3.2,4.3,27.5,3.2,4.3,27.5,3.2,5.33,27.5,3.2,4.3,27.5,,,4.2.4/4.2.5i)/4.2.6 +X,Reducible Loads,Truck Tractors,Steering Trailers - Manned,X,X,3.2,4.3,40,3.2,4.3,40,3.2,5.33,40,3.2,4.3,40,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors,Steering Trailers - Self/Remote,X,X,3.2,4.3,36,3.2,4.3,36,3.2,5.33,36,3.2,4.3,36,,,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers,,,,4.3,25,,4.3,25,,5.33,25,,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Truck Tractors - Stinger Steered,Semi-Trailers - Stinger Steered Automobile Transporters,,,,4.3,25,,4.3,25,,5.33,25,,4.3,25,1,1.2,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Trucks,Dollies,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Trucks,Full Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Trucks,None,,,3.2,4.3,16,3.2,4.3,16,3.2,4.4,16,3.2,4.3,16,1,6.5,4.2.4/4.2.5i)/4.2.6 +,Reducible Loads,Trucks,Pony Trailers,,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,1,6.5,4.2.4/4.2.5i)/4.2.6 +X,Scraper on Dollies,Truck Tractors,Dollies,X,,3.2,4.3,25,3.2,4.3,25,3.2,4.4,25,3.2,4.3,25,,,5.3.10.B. +,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Truck Tractors,None,,,2.9,4.3,,2.9,4.3,,2.9,4.3,,2.9,4.3,,,5,4.2.7.A.6/4.5.6.C. +,Tandem Jeep/Pole Trailer Loaded on Logging Truck,Trucks,None,,,2.9,4.3,,2.9,4.3,,2.9,4.3,,2.9,4.3,,,5,4.2.7.A.6/4.5.6.C. +,Tow Trucks And Disabled Vehicles,Tow Vehicles,None,,,,4.3,27.5,,4.3,27.5,,4.3,27.5,,4.3,27.5,,,5.3.14 +,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Truck Tractors,None,,,,4.3,13.5,,4.3,13.5,,4.3,13.5,,4.3,13.5,,5,4.2.7.A.6/4.5.6.D. +,Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck,Trucks,None,,,,4.3,13.5,,4.3,13.5,,4.3,13.5,,4.3,13.5,,5,4.2.7.A.6/4.5.6.D. +,"Wood Chips, Residuals",Truck Tractors,Semi-Trailers - B-Trains,,,,4.45,27.5,,4.45,27.5,,4.45,27.5,,4.45,27.5,,,4.2.7.A.16/4.5.4 diff --git a/policy-engine/src/_examples/output-os-as-csv.ts b/policy-engine/src/_examples/output-os-as-csv.ts new file mode 100644 index 000000000..1d8222ca4 --- /dev/null +++ b/policy-engine/src/_examples/output-os-as-csv.ts @@ -0,0 +1,126 @@ +import { completePolicyConfig } from '../_test/policy-config/complete-in-progress.sample'; +import { Policy } from '../policy-engine'; +import { TrailerSize } from '../types'; +import { toCsv } from '@iwsio/json-csv-core'; + +// json-csv-core options object +const options = { + fields: [ + { name: 'noSelfIssue', label: 'No Self Issue', transform: (v: boolean) => v ? 'X' : '' }, + { name: 'commodity', label: 'Commodity' }, + { name: 'powerUnit', label: 'Power Unit' }, + { name: 'trailer', label: 'Trailer' }, + { name: 'jeep', label: 'Allow Jeep', transform: (v: boolean) => v ? 'X' : '' }, + { name: 'booster', label: 'Allow Booster', transform: (v: boolean) => v ? 'X' : '' }, + { name: 'lmn.width', label: 'LMN - Width' }, + { name: 'lmn.height', label: 'LMN - Height' }, + { name: 'lmn.length', label: 'LMN - Length' }, + { name: 'ktn.width', label: 'KTN - Width' }, + { name: 'ktn.height', label: 'KTN - Height' }, + { name: 'ktn.length', label: 'KTN - Length' }, + { name: 'pce.width', label: 'PCE - Width' }, + { name: 'pce.height', label: 'PCE - Height' }, + { name: 'pce.length', label: 'PCE - Length' }, + { name: 'bcd.width', label: 'BCD - Width' }, + { name: 'bcd.height', label: 'BCD - Height' }, + { name: 'bcd.length', label: 'BCD - Length' }, + { name: 'fp', label: 'ORBC FP' }, + { name: 'rp', label: 'ORBC RP' }, + ], +}; + +/** + * Converts the size dimensions stored in the complete policy json to csv + * format matching the format used by business SMEs to verify the size + * dimensions match official policy. + * @param pol Policy with the size dimension set to convert + * @returns CSV string matching input size dimension set format + */ +function sizeDimensionSestToCsv( + pol: Policy, +): string | null { + const sizeDimensionSet: Array = []; + + pol.policyDefinition.commodities.forEach((commodity) => { + const commodityName = commodity.name; + commodity.size?.powerUnits?.forEach((powerUnit) => { + const powerUnitName = pol.getPowerUnitTypes().get(powerUnit.type); + powerUnit.trailers.forEach((trailer) => { + const trailerName = pol.getTrailerTypes().get(trailer.type); + + let bcDefaultDimensions; + if (trailer.sizeDimensions && trailer.sizeDimensions.length > 0) { + const dim = trailer.sizeDimensions[0]; + bcDefaultDimensions = { + bcd: { + width: dim.w, + height: dim.h, + length: dim.l, + }, + fp: dim.fp, + rp: dim.rp, + } + } + + let dimensionSetEntry = { + noSelfIssue: !trailer.selfIssue, + commodity: commodityName, + powerUnit: powerUnitName, + trailer: trailerName, + jeep: trailer.jeep, + booster: trailer.booster, + lmn: getDimensionsForRegion(trailer, 'LMN'), + ktn: getDimensionsForRegion(trailer, 'KTN'), + pce: getDimensionsForRegion(trailer, 'PCE'), + ...bcDefaultDimensions, + }; + + sizeDimensionSet.push(dimensionSetEntry); + }); + }); + }); + + const csvString = toCsv(sizeDimensionSet, options); + return csvString; +} + +/** + * Extracts the dimensions for the trailer as it applies to the supplied + * region, supplying BC Default values if no region configuration is available. + * @param trailer Trailer size dimensions for the region + * @param dimRegion Region the size dimensions are for - must match a region + * id as stored in the policy configuration JSON + * @returns object with width, height, and length for policy specific to the + * region. Defaults to width, height, and length for bc default if no specific + * region values are configured for the trailer. + */ +function getDimensionsForRegion(trailer: TrailerSize, dimRegion: string): any { + if (trailer.sizeDimensions && trailer.sizeDimensions.length > 0) { + const dim = trailer.sizeDimensions[0]; + if (dim.regions && dim.regions.length > 0) { + const region = dim.regions.find((r) => r.region == dimRegion); + if (region) { + return { + width: region.w ?? dim.w, + height: region.h ?? dim.h, + length: region.l ?? dim.l, + } + } + } + + // Use BC Default if region not configured + return { + width: dim.w, + height: dim.h, + length: dim.l, + } + } + + // No dimensions in the input, return nothing + return null; +} + +const policy = new Policy(completePolicyConfig); +const csv = sizeDimensionSestToCsv(policy); + +console.log(csv); \ No newline at end of file diff --git a/policy-engine/src/_examples/update-os-from-csv.ts b/policy-engine/src/_examples/update-os-from-csv.ts index 1faf277f4..b61e795bf 100644 --- a/policy-engine/src/_examples/update-os-from-csv.ts +++ b/policy-engine/src/_examples/update-os-from-csv.ts @@ -11,18 +11,34 @@ type DimensionEntry = { trailer: TrailerSize; }; +enum ColumnNumbers { + NoSelfIssue = 0, + Commodity = 1, + PowerUnit = 2, + Trailer = 3, + AllowJeep = 4, + AllowBooster = 5, + FirstWidth = 6, + DefaultWidth = 15, + FrontProjection = 18, + RearProjection = 19, +} + function csvRowToObject( row: Array, pol: Policy, ): DimensionEntry | null { - const commodityId = getIdFromName(pol.policyDefinition.commodities, row[1]); + const commodityId = getIdFromName( + pol.policyDefinition.commodities, + row[ColumnNumbers.Commodity], + ); const puId = getIdFromName( pol.policyDefinition.vehicleTypes.powerUnitTypes, - row[2].trim(), + row[ColumnNumbers.PowerUnit].trim(), ); const trId = getIdFromName( pol.policyDefinition.vehicleTypes.trailerTypes, - row[4].trim(), + row[ColumnNumbers.Trailer].trim(), ); let entryObject: DimensionEntry | null = null; if (commodityId && puId && trId) { @@ -31,16 +47,16 @@ function csvRowToObject( powerUnit: puId, trailer: { type: trId, - jeep: row[5] == 'X', - booster: row[6] == 'X', - selfIssue: row[0] != 'X', + jeep: row[ColumnNumbers.AllowJeep] == 'X' || row[ColumnNumbers.AllowJeep] == 'x', + booster: row[ColumnNumbers.AllowBooster] == 'X' || row[ColumnNumbers.AllowBooster] == 'x', + selfIssue: row[ColumnNumbers.NoSelfIssue] != 'X' && row[ColumnNumbers.NoSelfIssue] != 'x', }, }; const sizeDimension: SizeDimension = {}; - const fp = parseFloat(row[19]); - const rp = parseFloat(row[20]); + const fp = parseFloat(row[ColumnNumbers.FrontProjection]); + const rp = parseFloat(row[ColumnNumbers.RearProjection]); if (!isNaN(fp)) { sizeDimension.fp = fp; } @@ -49,9 +65,9 @@ function csvRowToObject( } // Populate the BC Default dimensions - const bcWidth = parseFloat(row[16]); - const bcHeight = parseFloat(row[17]); - const bcLength = parseFloat(row[18]); + const bcWidth = parseFloat(row[ColumnNumbers.DefaultWidth]); + const bcHeight = parseFloat(row[ColumnNumbers.DefaultWidth + 1]); + const bcLength = parseFloat(row[ColumnNumbers.DefaultWidth + 2]); if (!isNaN(bcWidth)) sizeDimension.w = bcWidth; if (!isNaN(bcHeight)) sizeDimension.h = bcHeight; if (!isNaN(bcLength)) sizeDimension.l = bcLength; @@ -59,9 +75,9 @@ function csvRowToObject( const regionIds: Array = ['LMN', 'KTN', 'PCE']; // Populate the 3 region dimensions for (let i = 0; i < regionIds.length; i++) { - const w = parseFloat(row[7 + i * 3]); - const h = parseFloat(row[8 + i * 3]); - const l = parseFloat(row[9 + i * 3]); + const w = parseFloat(row[ColumnNumbers.FirstWidth + i * 3]); + const h = parseFloat(row[ColumnNumbers.FirstWidth + 1 + i * 3]); + const l = parseFloat(row[ColumnNumbers.FirstWidth + 2 + i * 3]); if ( (isNaN(w) || w == sizeDimension.w) && (isNaN(h) || h == sizeDimension.h) && @@ -90,7 +106,7 @@ function csvRowToObject( return entryObject; } else { console.log( - `No entry in policy config for commodity '${row[1]}' and/or power unit '${row[2]}' and/or trailer '${row[4]}'`, + `No entry in policy config for commodity '${row[ColumnNumbers.Commodity]}' and/or power unit '${row[ColumnNumbers.PowerUnit]}' and/or trailer '${row[ColumnNumbers.Trailer]}'`, ); return null; } @@ -142,12 +158,14 @@ function processCsvRow(row: any) { } } -fs.createReadStream('./os-dimensions-simplified-nodefault.csv') - .pipe(parse({ delimiter: ',', from_line: 1 })) +fs.createReadStream('./Single Trip Oversize Dimension Set.csv') + .pipe(parse({ delimiter: ',', from_line: 3 })) .on('data', function (row) { processCsvRow(row); }) .on('end', function () { - console.log(JSON.stringify(policy.policyDefinition, null, ' ')); + console.log( + JSON.stringify(policy.policyDefinition.commodities, null, ' '), + ); //console.log(JSON.stringify(policy.policyDefinition)); }); diff --git a/policy-engine/src/_test/policy-config/complete-in-progress.sample.ts b/policy-engine/src/_test/policy-config/complete-in-progress.sample.ts index 217721584..1c8ee270a 100644 --- a/policy-engine/src/_test/policy-config/complete-in-progress.sample.ts +++ b/policy-engine/src/_test/policy-config/complete-in-progress.sample.ts @@ -784,6 +784,24 @@ export const completePolicyConfig: PolicyDefinition = { }, }, }, + { + conditions: { + not: { + fact: 'permitData', + operator: 'stringMinimumLength', + value: 1, + path: '$.permittedCommodity.loadDescription', + }, + }, + event: { + type: 'violation', + params: { + message: 'Commodity load description is required', + code: 'field-validation-error', + fieldReference: 'permitData.permittedCommodity.loadDescription', + }, + }, + }, ], costRules: [ { @@ -1219,4275 +1237,4617 @@ export const completePolicyConfig: PolicyDefinition = { }, commodities: [ { - id: 'NONEXXX', - name: 'None', - size: { - powerUnits: [ + "id": "NONEXXX", + "name": "None", + "size": { + "powerUnits": [ { - type: 'CONCRET', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - l: 15.5, - }, - ], - }, - ], + "type": "CONCRET", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 15.5 + } + ] + } + ] }, { - type: 'CRAFTAT', - trailers: [ - { - type: 'DOLLIES', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 3.8, - h: 4.3, - l: 25, - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 3.8, - h: 4.3, - l: 14, - regions: [ - { - region: 'PCE', - l: 15, - }, - ], - }, - ], - }, - ], + "type": "CRAFTAT", + "trailers": [ + { + "type": "DOLLIES", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 3.8, + "h": 4.3, + "l": 25 + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 3.8, + "h": 4.3, + "l": 14, + "regions": [ + { + "region": "PCE", + "l": 15 + } + ] + } + ] + } + ] }, { - type: 'CRAFTMB', - trailers: [ - { - type: 'DOLLIES', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 3.8, - h: 4.3, - l: 25, - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 3.8, - h: 4.3, - l: 14, - regions: [ - { - region: 'PCE', - l: 15, - }, - ], - }, - ], - }, - ], + "type": "CRAFTMB", + "trailers": [ + { + "type": "DOLLIES", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 3.8, + "h": 4.3, + "l": 25 + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 3.8, + "h": 4.3, + "l": 14, + "regions": [ + { + "region": "PCE", + "l": 15 + } + ] + } + ] + } + ] }, { - type: 'DDCKBUS', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - h: 4.42, - regions: [ - { - region: 'LMN', - h: 4.3, - }, - { - region: 'KTN', - h: 4.3, - }, - { - region: 'PCE', - h: 4.3, - }, - ], - }, - ], - }, - ], + "type": "DDCKBUS", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.42, + "l": 12.5, + "regions": [ + { + "region": "LMN", + "h": 4.3 + }, + { + "region": "KTN", + "h": 4.3 + }, + { + "region": "PCE", + "h": 4.3 + } + ] + } + ] + } + ] }, { - type: 'GRADERS', - trailers: [ - { - type: 'FEPNYTR', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.2, - h: 4.3, - l: 31, - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - w: 3.5, - h: 4.4, - l: 12.5, - regions: [ - { - region: 'LMN', - h: 4.3, - }, - { - region: 'KTN', - h: 4.3, - }, - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], + "type": "GRADERS", + "trailers": [ + { + "type": "FEPNYTR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.2, + "h": 4.3, + "l": 31 + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.5, + "h": 4.4, + "l": 12.5, + "regions": [ + { + "region": "LMN", + "h": 4.3 + }, + { + "region": "KTN", + "h": 4.3 + }, + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] }, { - type: 'BUSTRLR', - trailers: [ - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [{}], - }, - ], + "type": "BUSTRLR", + "trailers": [ + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 20 + } + ] + } + ] }, { - type: 'LOGOFFH', - trailers: [ - { - type: 'STLOGNG', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 4.4, - }, - ], - }, - ], + "type": "LOGOFFH", + "trailers": [ + { + "type": "STLOGNG", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 4.4, + "h": 4.15, + "l": 23 + } + ] + } + ] }, { - type: 'LCVRMDB', - trailers: [ - { - type: 'SEMITRL', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - l: 32, - regions: [ - { - region: 'PCE', - l: 31, - }, - ], - }, - ], - }, - ], + "type": "LCVRMDB", + "trailers": [ + { + "type": "SEMITRL", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 32, + "regions": [ + { + "region": "PCE", + "l": 31 + } + ] + } + ] + } + ] }, { - type: 'LCVTPDB', - trailers: [ - { - type: 'SEMITRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - l: 41, - }, - ], - }, - ], + "type": "LCVTPDB", + "trailers": [ + { + "type": "SEMITRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 41 + } + ] + } + ] }, { - type: 'LWBTRCT', - trailers: [ - { - type: 'SEMITRL', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 2.6, - h: 4.15, - l: 23, - }, - ], - }, - ], + "type": "LWBTRCT", + "trailers": [ + { + "type": "SEMITRL", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 23 + } + ] + } + ] }, { - type: 'PICKRTT', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 2.6, - h: 4.15, - l: 16, - }, - ], - }, - { - type: 'STCRANE', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 2.6, - h: 4.15, - l: 25, - }, - ], - }, - { - type: 'STROPRT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 40, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STRSELF', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 36, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], + "type": "PICKRTT", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 16 + } + ] + }, + { + "type": "STCRANE", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 25 + } + ] + }, + { + "type": "STROPRT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 40, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STRSELF", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 36, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] }, { - type: 'SCRAPER', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - l: 12.5, - }, - ], - }, - ], + "type": "SCRAPER", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 12.5 + } + ] + } + ] }, { - type: 'TRKTRAC', - trailers: [ - { - type: 'FECVYER', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 4, - rp: 9.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FEDRMMX', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FEBGHSE', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 4.26, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FESEMTR', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FEWHELR', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'ODTRLEX', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - w: 3.65, - }, - ], - }, - { - type: 'REDIMIX', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - h: 4.3, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STREEFR', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - h: 4.3, - }, - ], - }, - { - type: 'STNTSHC', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [{}], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - l: 32, - }, - ], - }, - ], - }, - { - type: 'STROPRT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - h: 4.15, - l: 40, - }, - ], - }, - { - type: 'STRSELF', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - l: 36, - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "FECVYER", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 4, + "rp": 9.5, + "w": 3.8, + "h": 4.72, + "l": 31, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FEDRMMX", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.8, + "h": 4.72, + "l": 31, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FEBGHSE", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 4.26, + "h": 4.72, + "l": 31, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FESEMTR", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.8, + "h": 4.72, + "l": 31, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FEWHELR", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 31, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "ODTRLEX", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.65, + "h": 4.15, + "l": 23 + } + ] + }, + { + "type": "REDIMIX", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 23 + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.3, + "l": 31, + "regions": [ + { + "region": "LMN", + "l": 23 + }, + { + "region": "KTN", + "l": 23 + }, + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STREEFR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.3, + "l": 23 + } + ] + }, + { + "type": "STNTSHC", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 5, + "rp": 2.25, + "w": 2.6, + "h": 4.15, + "l": 20 + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "l": 32 + } + ] + } + ] + }, + { + "type": "STROPRT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 40 + } + ] + }, + { + "type": "STRSELF", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 36 + } + ] + } + ] }, { - type: 'REGTRCK', - trailers: [ - { - type: 'FECVYPT', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 4, - rp: 9.5, - w: 3.2, - h: 4.3, - l: 31, - }, - ], - }, - { - type: 'FEPNYTR', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.2, - h: 4.3, - l: 31, - }, - ], - }, - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 6.5, - w: 3.8, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'MHMBSHL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'MHMBSHG', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 16, - regions: [ - { - region: 'PCE', - h: 4.4, - }, - ], - }, - ], - }, - { - type: 'ODTRLEX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.65, - }, - ], - }, - { - type: 'PMHWAAX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.9, - w: 4.4, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 6.5, - w: 3.2, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], + "type": "REGTRCK", + "trailers": [ + { + "type": "FECVYPT", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 4, + "rp": 9.5, + "w": 3.2, + "h": 4.3, + "l": 31 + } + ] + }, + { + "type": "FEPNYTR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.2, + "h": 4.3, + "l": 31 + } + ] + }, + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 3.8, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "MHMBSHL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 5, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "MHMBSHG", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 6, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "w": 6.1, + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 16, + "regions": [ + { + "region": "PCE", + "h": 4.4 + } + ] + } + ] + }, + { + "type": "ODTRLEX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.65, + "h": 4.15, + "l": 23 + } + ] + }, + { + "type": "PMHWAAX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.9, + "w": 4.4, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] }, { - type: 'PLOWBLD', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - }, - ], - }, - ], - }, - ], - }, + "type": "PLOWBLD", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.2, + "h": 4.15, + "l": 12.5 + } + ] + } + ] + } + ] + } }, { - id: 'DOGLOGG', - name: 'Doglogger/Sjostrum Trailers (decked)', - size: { - powerUnits: [ + "id": "DOGLOGG", + "name": "Doglogger/Sjostrum Trailers (decked)", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - l: 13.5, - }, - ], - }, - ], - }, - ], - }, + "type": "TRKTRAC", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 2.6, + "h": 4.15, + "l": 13.5 + } + ] + } + ] + } + ] + } }, { - id: 'GRTBBUK', - name: 'Grader, Tractor Blades, Buckets', - size: { - powerUnits: [ + "id": "GRTBBUK", + "name": "Grader, Tractor Blades, Buckets", + "size": { + "powerUnits": [ { - type: 'GRADERS', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 4.4, - }, - ], - }, - ], - }, - ], - }, + "type": "GRADERS", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 0, + "rp": 0, + "w": 4.4, + "h": 4.15, + "l": 12.5 + } + ] + } + ] + } + ] + } }, { - id: 'HAYRACK', - name: 'Hayrack Semi-Trailer with a Folded Chassis/Empty Piggyback', - size: { - powerUnits: [ + "id": "HAYRACK", + "name": "Hayrack Semi-Trailer with a Folded Chassis/Empty Piggyback", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'STLOGNG', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - h: 4.15, - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "STLOGNG", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 2.6, + "h": 4.15, + "l": 23 + } + ] + } + ] + } + ] + } + }, + { + "id": "IMCONTN", + "name": "Intermodal Containers", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.4, + "l": 26 + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.4, + "l": 27.5 + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.4, + "l": 23 + } + ] + } + ] }, - ], - }, + { + "type": "REGTRCK", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.4, + "l": 12.5 + } + ] + } + ] + } + ] + } }, { - id: 'IMCONTN', - name: 'Intermodal Containers', - size: { - powerUnits: [ + "id": "IMCONWS", + "name": "Intermodal Containers without Sides", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - h: 4.4, - l: 26, - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - h: 4.4, - l: 27.5, - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - h: 4.4, - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 4.4, + "h": 4.72, + "l": 23 + } + ] + } + ] + } + ] + } + }, + { + "id": "LPBOOMS", + "name": "Logs, Poles And Boomsticks (Up To 20.1)", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "LOGLGCY", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 5, + "w": 2.6, + "h": 4.15, + "l": 25 + } + ] + }, + { + "type": "POLETRL", + "jeep": true, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 26 + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "HIBOFLT", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 25 + } + ] + }, + { + "type": "STLOGNG", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 25 + } + ] + } + ] + } + ] + } + }, + { + "id": "LPBOOML", + "name": "Logs, Poles And Boomsticks (Over 20.1)", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "LOGFULL", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 10, + "rp": 10, + "w": 2.6, + "h": 4.15, + "l": 40 + } + ] + }, + { + "type": "POLETRL", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 8, + "rp": 9, + "w": 2.6, + "h": 4.15, + "l": 40 + } + ] + }, + { + "type": "STROPRT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 10, + "w": 2.6, + "h": 4.15, + "l": 40 + } + ] + }, + { + "type": "STRSELF", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 10, + "w": 2.6, + "h": 4.15, + "l": 36 + } + ] + } + ] + } + ] + } + }, + { + "id": "MFHOMES", + "name": "Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "SEMITRL", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 7.5, + "w": 5, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 7.5, + "w": 5, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33, + "l": 36 + } + ] + } + ] + } + ] }, { - type: 'REGTRCK', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - h: 4.4, - }, - ], - }, - ], + "type": "REGTRCK", + "trailers": [ + { + "type": "DOLLIES", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 5, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "FLOATTR", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 5, + "h": 4.57, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 5, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33, + "l": 36 + } + ] + } + ] + } + ] + } + ] + } + }, + { + "id": "MFHOMEL", + "name": "Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "SEMITRL", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 7.5, + "w": 6, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "w": 6.1, + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 7.5, + "w": 6, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "w": 6.1, + "h": 5.33, + "l": 36 + } + ] + } + ] + } + ] }, - ], - }, + { + "type": "REGTRCK", + "trailers": [ + { + "type": "DOLLIES", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 6, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "w": 6.1, + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "FLOATTR", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 6, + "h": 4.57, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "w": 6.1, + "h": 5.33, + "l": 36 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.9, + "w": 6, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "w": 6.1, + "h": 5.33, + "l": 36 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'IMCONWS', - name: 'Intermodal Containers without Sides', - size: { - powerUnits: [ + "id": "PARKMHS", + "name": "Park Model Homes", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 4.4, - h: 4.72, - }, - ], - }, - ], + "type": "REGTRCK", + "trailers": [ + { + "type": "DOLLIES", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.9, + "w": 4.4, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FLOATTR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.9, + "w": 4.4, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.9, + "w": 4.4, + "h": 4.88, + "l": 31.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] + } + ] + } + }, + { + "id": "PIPESTL", + "name": "Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 31 + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5 + } + ] + }, + { + "type": "STSTEER", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 36 + } + ] + }, + { + "type": "STROPRT", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 40 + } + ] + } + ] + } + ] + } + }, + { + "id": "REDUCBL", + "name": "Reducible Loads", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "STLOGNG", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "PLATFRM", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "SEMITRL", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 26, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSTEER", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWHELR", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWIDWH", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STCRANE", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STROPRT", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 40, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STRSELF", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 36, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] }, - ], - }, + { + "type": "PICKRTT", + "trailers": [ + { + "type": "SEMITRL", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] + }, + { + "type": "STINGER", + "trailers": [ + { + "type": "SEMITRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1.2, + "w": 2.6, + "h": 4.3, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSTNGR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1.2, + "w": 2.6, + "h": 4.3, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] + }, + { + "type": "REGTRCK", + "trailers": [ + { + "type": "DOLLIES", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 4.4 + } + ] + } + ] + }, + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 4.4 + } + ] + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 16, + "regions": [ + { + "region": "PCE", + "h": 4.4 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 4.4 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'LPBOOMS', - name: 'Logs, Poles And Boomsticks (Up To 20.1)', - size: { - powerUnits: [ + "id": "SCRAPER", + "name": "Scraper on Dollies", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 2.9, - l: 27.5, - }, - ], - }, - { - type: 'LOGLGCY', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 5, - w: 2.6, - l: 25, - }, - ], - }, - { - type: 'POLETRL', - jeep: true, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 2.9, - l: 27.5, - }, - ], - }, - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 2.9, - l: 26, - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 2.9, - l: 27.5, - }, - ], - }, - { - type: 'HIBOFLT', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 2.9, - l: 25, - }, - ], - }, - { - type: 'STLOGNG', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 2.9, - l: 25, - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "DOLLIES", + "jeep": true, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.2, + "h": 4.3, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 4.4 + } + ] + } + ] + } + ] + } + ] + } + }, + { + "id": "OILFILD", + "name": "Oil Field Equipment", + "size": { + "powerUnits": [ + { + "type": "OGBEDTK", + "trailers": [ + { + "type": "EXPANDO", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.3, + "l": 27.5 + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 3.3, + "h": 4.3, + "l": 14 + } + ] + }, + { + "type": "OGOSFDT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.3, + "h": 4.3, + "l": 23 + } + ] + } + ] }, - ], - }, + { + "type": "OGOILSW", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 3.2, + "h": 4.3, + "l": 15 + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 25 + } + ] + } + ] + }, + { + "type": "OGSERVC", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 3.8, + "h": 4.3, + "l": 15 + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 3.8, + "h": 4.3, + "l": 23 + } + ] + } + ] + }, + { + "type": "OGSRRAH", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 15.5 + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 10, + "rp": 6.5, + "w": 2.9, + "h": 4.15, + "l": 23 + } + ] + } + ] + }, + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "OGOSFDT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.3, + "h": 4.3, + "l": 23 + } + ] + } + ] + } + ] + } }, { - id: 'LPBOOML', - name: 'Logs, Poles And Boomsticks (Over 20.1)', - size: { - powerUnits: [ + "id": "JPTRLOG", + "name": "Tandem Jeep/Pole Trailer Loaded on Logging Truck", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'LOGFULL', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 10, - rp: 10, - l: 40, - }, - ], - }, - { - type: 'POLETRL', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 8, - rp: 9, - l: 40, - }, - ], - }, - { - type: 'STROPRT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 10, - l: 40, - }, - ], - }, - { - type: 'STRSELF', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 10, - l: 36, - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 2.9, + "h": 4.3, + "l": 12.5 + } + ] + } + ] }, - ], - }, + { + "type": "REGTRCK", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 2.9, + "h": 4.3, + "l": 12.5 + } + ] + } + ] + } + ] + } }, { - id: 'MFHOMES', - name: 'Manufactured Homes, Modular Buildings, Structures and Houseboats (<= 5.0 m OAW)', - size: { - powerUnits: [ + "id": "TOWDISB", + "name": "Tow Trucks And Disabled Vehicles", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'SEMITRL', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 7.5, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 7.5, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - ], + "type": "TOWVEHC", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.3, + "l": 27.5 + } + ] + } + ] + } + ] + } + }, + { + "id": "TRQDLOG", + "name": "Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 2.6, + "h": 4.3, + "l": 13.5 + } + ] + } + ] }, { - type: 'REGTRCK', - trailers: [ - { - type: 'DOLLIES', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'FLOATTR', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.57, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - ], + "type": "REGTRCK", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 5, + "w": 2.6, + "h": 4.3, + "l": 13.5 + } + ] + } + ] + } + ] + } + }, + { + "id": "WOODCHP", + "name": "Wood Chips, Residuals", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.45, + "l": 27.5 + } + ] + } + ] + } + ] + } + }, + { + "id": "EMPTYXX", + "name": "Empty", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "LOGOWBK", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 0, + "rp": 0, + "w": 3.2, + "h": 4.15, + "l": 23 + } + ] + }, + { + "type": "PLATFRM", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.4, + "l": 31, + "regions": [ + { + "region": "LMN", + "h": 4.15 + }, + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.4, + "l": 27.5, + "regions": [ + { + "region": "LMN", + "h": 4.15 + }, + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.4, + "l": 31, + "regions": [ + { + "region": "LMN", + "h": 4.15 + }, + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWHELR", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "LMN", + "h": 4.15 + }, + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWIDWH", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "LMN", + "h": 4.15 + }, + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] }, - ], - }, + { + "type": "PICKRTT", + "trailers": [ + { + "type": "OGOSFDT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.15, + "l": 23 + } + ] + }, + { + "type": "PLATFRM", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 25, + "regions": [ + { + "region": "PCE", + "l": 27.5 + } + ] + } + ] + }, + { + "type": "STWHELR", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "STWIDWH", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.15, + "l": 27.5 + } + ] + } + ] + } + ] + } }, { - id: 'MFHOMEL', - name: 'Manufactured Homes, Modular Buildings, Structures and Houseboats (> 5.0 m OAW)', - size: { - powerUnits: [ + "id": "GRBBINS", + "name": "Garbage Bins", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'SEMITRL', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 7.5, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 7.5, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - ], + "type": "REGTRCK", + "trailers": [ + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 23 + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 12.5 + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 23 + } + ] + } + ] + } + ] + } + }, + { + "id": "LAMBEAM", + "name": "Laminated Beams", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "POLETRL", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 40 + } + ] + }, + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 31 + } + ] + } + ] + } + ] + } + }, + { + "id": "HAYLREC", + "name": "Hay Bales Large Rectangular", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.05, + "h": 4.3, + "l": 26, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.05, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.05, + "h": 4.4, + "l": 23, + "regions": [ + { + "region": "LMN", + "h": 4.3 + }, + { + "region": "KTN", + "h": 4.3 + }, + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.05, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + } + ] }, { - type: 'REGTRCK', - trailers: [ - { - type: 'DOLLIES', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'FLOATTR', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.57, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 1, - rp: 6.9, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - l: 36, - }, - ], - }, - ], - }, - ], + "type": "REGTRCK", + "trailers": [ + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.05, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.05, + "h": 4.3, + "l": 12.5, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.05, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + } + ] + } + ] + } + }, + { + "id": "HAYROND", + "name": "Hay Bales Round", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.5, + "h": 4.3, + "l": 26, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.5, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + } + ] }, - ], - }, + { + "type": "REGTRCK", + "trailers": [ + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.5, + "h": 4.3, + "l": 12.5, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'PARKMHS', - name: 'Park Model Homes', - size: { - powerUnits: [ + "id": "HAYSREC", + "name": "Hay Bales Small Rectangular", + "size": { + "powerUnits": [ { - type: 'REGTRCK', - trailers: [ - { - type: 'DOLLIES', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.9, - w: 4.4, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FLOATTR', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.9, - w: 4.4, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.9, - w: 4.4, - h: 4.88, - l: 31.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.05, + "h": 4.3, + "l": 26, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.05, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.05, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.05, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + } + ] }, - ], - }, + { + "type": "REGTRCK", + "trailers": [ + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.05, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.05, + "h": 4.3, + "l": 12.5, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.05, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "h": 4.8 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'PIPESTL', - name: 'Pipe And Steel Products (Rebar, Pilings, Reinforcing Steel, Etc.)', - size: { - powerUnits: [ + "id": "BRGBEAM", + "name": "Bridge Beams", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 31, - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 27.5, - }, - ], - }, - { - type: 'STSTEER', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 36, - }, - ], - }, - { - type: 'STROPRT', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 40, - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "POLETRL", + "jeep": true, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 31 + } + ] + }, + { + "type": "SEMITRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.2, + "h": 4.3, + "l": 31, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] + } + ] + } + }, + { + "id": "NONREDU", + "name": "Non-Reducible Loads", + "size": { + "powerUnits": [ + { + "type": "TRKTRAC", + "trailers": [ + { + "type": "STLOGNG", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.8, + "h": 4.15, + "l": 27.5 + } + ] + }, + { + "type": "PLATFRM", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "SEMITRL", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.4, + "l": 26, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.4, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.4, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.4, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSTEER", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWHELR", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWIDWH", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STCRANE", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STROPRT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 40, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STRSELF", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 36, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] }, - ], - }, + { + "type": "PICKRTT", + "trailers": [ + { + "type": "OGOSFDT", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.3, + "h": 4.3, + "l": 23 + } + ] + }, + { + "type": "SEMITRL", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOEXP", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.4, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.4, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSTEER", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWHELR", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STWIDWH", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STCRANE", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STROPRT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 40, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STRSELF", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 36, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] + }, + { + "type": "STINGER", + "trailers": [ + { + "type": "SEMITRL", + "jeep": false, + "booster": false, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1.2, + "w": 2.6, + "h": 4.88, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSTNGR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1.2, + "w": 2.6, + "h": 4.88, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] + }, + { + "type": "REGTRCK", + "trailers": [ + { + "type": "DOLLIES", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 16, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 6.5, + "w": 5, + "h": 4.88, + "l": 25, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'REDUCBL', - name: 'Reducible Loads', - size: { - powerUnits: [ + "id": "AUTOCRR", + "name": "Auto Carrier, Campers And Boats (Stinger Steered Transporters Only)", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'STLOGNG', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.15, - l: 27.5, - }, - ], - }, - { - type: 'PLATFRM', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'SEMITRL', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 26, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSTEER', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWHELR', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWIDWH', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STCRANE', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STROPRT', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 40, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STRSELF', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 36, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - { - type: 'PICKRTT', - trailers: [ - { - type: 'SEMITRL', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 3.2, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - { - type: 'STINGER', - trailers: [ - { - type: 'SEMITRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 1.2, - h: 4.3, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSTNGR', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 1.2, - h: 4.3, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - { - type: 'REGTRCK', - trailers: [ - { - type: 'DOLLIES', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 25, - regions: [ - { - region: 'PCE', - h: 4.4, - }, - ], - }, - ], - }, - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 25, - regions: [ - { - region: 'PCE', - h: 4.4, - }, - ], - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 16, - regions: [ - { - region: 'PCE', - h: 4.4, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 25, - regions: [ - { - region: 'PCE', - h: 4.4, - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'SCRAPER', - name: 'Scraper on Dollies', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'DOLLIES', - jeep: true, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.3, - l: 25, - regions: [ - { - region: 'PCE', - h: 4.4, - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'OILFILD', - name: 'Oil Field Equipment', - size: { - powerUnits: [ - { - type: 'OGBEDTK', - trailers: [ - { - type: 'EXPANDO', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - h: 4.3, - l: 27.5, - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - w: 3.3, - h: 4.3, - l: 14, - }, - ], - }, - { - type: 'OGOSFDT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.3, - h: 4.3, - l: 23, - }, - ], - }, - ], - }, - { - type: 'OGOILSW', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - w: 3.2, - h: 4.3, - l: 15, - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 25, - }, - ], - }, - ], - }, - { - type: 'OGSERVC', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 3.8, - h: 4.3, - l: 15, - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 3.8, - h: 4.3, - l: 23, - }, - ], - }, - ], - }, - { - type: 'OGSRRAH', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 2.9, - h: 4.15, - l: 15.5, - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 10, - rp: 6.5, - w: 2.9, - h: 4.15, - l: 23, - }, - ], - }, - ], - }, - { - type: 'TRKTRAC', - trailers: [ - { - type: 'OGOSFDT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.3, - h: 4.3, - l: 23, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'JPTRLOG', - name: 'Tandem Jeep/Pole Trailer Loaded on Logging Truck', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - w: 2.9, - h: 4.3, - }, - ], - }, - ], - }, - { - type: 'REGTRCK', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - w: 2.9, - h: 4.3, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'TOWDISB', - name: 'Tow Trucks And Disabled Vehicles', - size: { - powerUnits: [ - { - type: 'TOWVEHC', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - h: 4.3, - l: 27.5, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'TRQDLOG', - name: 'Tri-Axle or Quad Axle Full Trailer Loaded on Logging Truck', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - h: 4.3, - l: 13.5, - }, - ], - }, - ], - }, - { - type: 'REGTRCK', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 5, - h: 4.3, - l: 13.5, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'WOODCHP', - name: 'Wood Chips, Residuals', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - h: 4.45, - l: 27.5, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'EMPTYXX', - name: 'Empty', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'LOGOWBK', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 0, - rp: 0, - w: 3.2, - }, - ], - }, - { - type: 'PLATFRM', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - l: 31, - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - l: 27.5, - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - l: 31, - }, - ], - }, - { - type: 'STWHELR', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWIDWH', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STCRANE', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - { - type: 'PICKRTT', - trailers: [ - { - type: 'OGOSFDT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.2, - h: 4.3, - l: 23, - }, - ], - }, - { - type: 'PLATFRM', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - l: 25, - regions: [ - { - region: 'PCE', - l: 27.5, - }, - ], - }, - ], - }, - { - type: 'STWHELR', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWIDWH', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STCRANE', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 3.2, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'GRBBINS', - name: 'Garbage Bins', - size: { - powerUnits: [ - { - type: 'REGTRCK', - trailers: [ - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'LAMBEAM', - name: 'Laminated Beams', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'POLETRL', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - l: 40, - }, - ], - }, - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - l: 31, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'HAYLREC', - name: 'Hay Bales Large Rectangular', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.05, - h: 4.3, - l: 26, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.05, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.05, - h: 4.4, - regions: [ - { - region: 'LMN', - h: 4.3, - }, - { - region: 'KTN', - h: 4.3, - }, - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - ], - }, - { - type: 'REGTRCK', - trailers: [ - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'HAYROND', - name: 'Hay Bales Round', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.5, - h: 4.3, - l: 26, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.5, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - ], - }, - { - type: 'REGTRCK', - trailers: [ - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'HAYSREC', - name: 'Hay Bales Small Rectangular', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.05, - h: 4.3, - l: 26, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.05, - h: 4.3, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - ], - }, - { - type: 'REGTRCK', - trailers: [ - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.05, - h: 4.3, - regions: [ - { - region: 'PCE', - h: 4.8, - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'BRGBEAM', - name: 'Bridge Beams', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'POLETRL', - jeep: true, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - l: 31, - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'NONREDU', - name: 'Non-Reducible Loads', - size: { - powerUnits: [ - { - type: 'TRKTRAC', - trailers: [ - { - type: 'STLOGNG', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - w: 3.8, - h: 4.15, - l: 27.5, - }, - ], - }, - { - type: 'PLATFRM', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'SEMITRL', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.4, - l: 26, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.4, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.4, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.4, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSTEER', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWHELR', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWIDWH', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STCRANE', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STROPRT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 40, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STRSELF', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 36, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - { - type: 'PICKRTT', - trailers: [ - { - type: 'OGOSFDT', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.3, - h: 4.3, - l: 23, - }, - ], - }, - { - type: 'SEMITRL', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 5, - h: 4.88, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOEXP', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 5, - h: 4.4, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - w: 5, - h: 4.4, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - w: 5, - h: 4.88, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSTEER', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWHELR', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STWIDWH', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STCRANE', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 27.5, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STROPRT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 40, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STRSELF', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - w: 5, - h: 4.88, - l: 36, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - { - type: 'STINGER', - trailers: [ - { - type: 'SEMITRL', - jeep: false, - booster: false, - selfIssue: false, - sizeDimensions: [ - { - fp: 1, - rp: 1.2, - h: 4.88, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSTNGR', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 1.2, - h: 4.88, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - { - type: 'REGTRCK', - trailers: [ - { - type: 'DOLLIES', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 5, - h: 4.88, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 5, - h: 4.88, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 5, - h: 4.88, - l: 16, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 6.5, - w: 5, - h: 4.88, - l: 25, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - id: 'AUTOCRR', - name: 'Auto Carrier, Campers And Boats (Stinger Steered Transporters Only)', - size: { - powerUnits: [ - { - type: 'STINGER', - trailers: [ - { - type: 'STSTNGR', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 1, - rp: 1.2, - h: 4.4, - l: 25, - regions: [ - { - region: 'LMN', - h: 4.3, - }, - { - region: 'KTN', - h: 4.3, - }, - { - region: 'PCE', - h: 4.88, - }, - ], - }, - ], - }, - ], - }, - ], - }, + "type": "STINGER", + "trailers": [ + { + "type": "STSTNGR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1.2, + "w": 2.6, + "h": 4.4, + "l": 25, + "regions": [ + { + "region": "LMN", + "h": 4.3 + }, + { + "region": "KTN", + "h": 4.3 + }, + { + "region": "PCE", + "h": 4.88 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'HAYRNPR', - name: 'Hay Bales (Round) Peace River Only', - size: { - powerUnits: [ + "id": "HAYRNPR", + "name": "Hay Bales (Round) Peace River Only", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'STACTRN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.5, - h: 4.3, - l: 26, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STBTRAN', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'HIBOFLT', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "STACTRN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.5, + "h": 4.3, + "l": 26, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STBTRAN", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.5, + "h": 4.3, + "l": 27.5, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "HIBOFLT", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + } + ] }, { - type: 'REGTRCK', - trailers: [ - { - type: 'FULLLTL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.5, - h: 4.3, - regions: [ - { - region: 'PCE', - w: 3.84, - h: 4.8, - }, - ], - }, - ], - }, - ], - }, - ], - }, + "type": "REGTRCK", + "trailers": [ + { + "type": "FULLLTL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 3.5, + "h": 4.3, + "l": 12.5, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.5, + "h": 4.3, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.84, + "h": 4.8 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'BRSHCUT', - name: 'Brushcutters (Peace Only)', - size: { - powerUnits: [ + "id": "BRSHCUT", + "name": "Brushcutters (Peace Only)", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'SEMITRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - regions: [ - { - region: 'PCE', - w: 4.57, - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'STSDBDK', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - regions: [ - { - region: 'PCE', - w: 4.57, - h: 5.33, - }, - ], - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "SEMITRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 4.57, + "h": 5.33 + } + ] + } + ] + }, + { + "type": "STSDBDK", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 2.6, + "h": 4.15, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 4.57, + "h": 5.33 + } + ] + } + ] + } + ] }, { - type: 'REGTRCK', - trailers: [ - { - type: 'NONEXXX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - regions: [ - { - region: 'PCE', - w: 4.57, - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'PONYTRL', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - regions: [ - { - region: 'PCE', - w: 3.8, - h: 5.33, - }, - ], - }, - ], - }, - ], - }, - ], - }, + "type": "REGTRCK", + "trailers": [ + { + "type": "NONEXXX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 12.5, + "regions": [ + { + "region": "PCE", + "w": 4.57, + "h": 5.33 + } + ] + } + ] + }, + { + "type": "PONYTRL", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 1, + "w": 2.6, + "h": 4.15, + "l": 23, + "regions": [ + { + "region": "PCE", + "w": 3.8, + "h": 5.33 + } + ] + } + ] + } + ] + } + ] + } }, { - id: 'FIXEDEQ', - name: 'Fixed Equipment', - size: { - powerUnits: [ + "id": "FIXEDEQ", + "name": "Fixed Equipment", + "size": { + "powerUnits": [ { - type: 'TRKTRAC', - trailers: [ - { - type: 'FECVYER', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 4, - rp: 9.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FEDRMMX', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FEBGHSE', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 4.26, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FESEMTR', - jeep: true, - booster: true, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FEWHELR', - jeep: true, - booster: true, - selfIssue: false, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - ], + "type": "TRKTRAC", + "trailers": [ + { + "type": "FECVYER", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 4, + "rp": 9.5, + "w": 3.8, + "h": 4.72, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FEDRMMX", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.8, + "h": 4.72, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FEBGHSE", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 4.26, + "h": 4.72, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FESEMTR", + "jeep": true, + "booster": true, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.8, + "h": 4.72, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FEWHELR", + "jeep": true, + "booster": true, + "selfIssue": false, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.8, + "h": 4.72, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + } + ] }, { - type: 'REGTRCK', - trailers: [ - { - type: 'FECVYPT', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 4, - rp: 9.5, - w: 3.2, - h: 4.3, - l: 31, - }, - ], - }, - { - type: 'FEDRMMX', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 3.8, - h: 4.72, - l: 31, - regions: [ - { - region: 'PCE', - h: 5.33, - }, - ], - }, - ], - }, - { - type: 'FEPNYTR', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - rp: 4, - w: 3.2, - h: 4.3, - l: 31, - }, - ], - }, - { - type: 'FEBGHSE', - jeep: false, - booster: false, - selfIssue: true, - sizeDimensions: [ - { - fp: 3, - rp: 6.5, - w: 4.26, - h: 4.72, - l: 31, - }, - ], - }, - ], - }, - ], - }, - }, + "type": "REGTRCK", + "trailers": [ + { + "type": "FECVYPT", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 4, + "rp": 9.5, + "w": 3.2, + "h": 4.3 + } + ] + }, + { + "type": "FEDRMMX", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 3.8, + "h": 4.72, + "regions": [ + { + "region": "PCE", + "h": 5.33 + } + ] + } + ] + }, + { + "type": "FEPNYTR", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 1, + "rp": 4, + "w": 3.2, + "h": 4.3 + } + ] + }, + { + "type": "FEBGHSE", + "jeep": false, + "booster": false, + "selfIssue": true, + "sizeDimensions": [ + { + "fp": 3, + "rp": 6.5, + "w": 4.26, + "h": 4.72 + } + ] + } + ] + } + ] + } + } ], globalSizeDefaults: { fp: 3, From cc90fa6696f3ab22b29d294b11686343b25c1419 Mon Sep 17 00:00:00 2001 From: zgong-gov <123983557+zgong-gov@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:44:26 -0700 Subject: [PATCH 43/58] ORV2-2338 Bug Fix (#1641) --- frontend/src/common/helpers/equality.ts | 77 +++++++ frontend/src/common/helpers/util.ts | 43 ---- frontend/src/common/hooks/useMemoizedArray.ts | 33 +++ .../src/common/hooks/useMemoizedObject.ts | 26 +++ .../manageVehicles/helpers/vehicleSubtypes.ts | 18 ++ .../permits/context/ApplicationFormContext.ts | 5 +- .../features/permits/helpers/conditions.ts | 84 ++++++++ .../features/permits/helpers/dateSelection.ts | 8 +- .../src/features/permits/helpers/equality.ts | 31 +-- .../src/features/permits/helpers/permitLCV.ts | 54 +---- .../src/features/permits/helpers/permitLOA.ts | 22 +- .../permits/helpers/permitVehicles.ts | 71 ++++++- .../hooks/useApplicationFormContext.ts | 133 +++++++++--- .../hooks/useInitApplicationFormData.ts | 39 ++-- .../permits/hooks/usePermitConditions.ts | 34 ++- .../permits/hooks/usePermitDateSelection.ts | 19 +- .../permits/hooks/usePermitVehicleForLOAs.ts | 113 +++++++++- .../Amend/components/AmendPermitForm.tsx | 20 +- .../pages/Amend/hooks/useAmendPermitForm.ts | 45 ++-- .../pages/Application/ApplicationForm.tsx | 9 +- .../components/form/ConditionsTable.tsx | 50 ++--- .../Application/components/form/LOATable.tsx | 4 +- .../components/form/PermitDetails.tsx | 13 +- .../components/form/PermitForm.tsx | 46 ++--- .../{PermitLOA.scss => PermitLOASection.scss} | 8 +- .../{PermitLOA.tsx => PermitLOASection.tsx} | 21 +- .../form/VehicleDetails/VehicleDetails.tsx | 193 +++--------------- .../customFields/SelectVehicleDropdown.tsx | 22 +- .../components/form/tests/helpers/prepare.tsx | 17 +- .../components/review/PermitReview.tsx | 4 +- .../review/ReviewContactDetails.tsx | 6 +- .../components/review/ReviewPermitDetails.tsx | 7 +- .../components/review/ReviewPermitLOAs.tsx | 4 +- .../components/review/ReviewVehicleInfo.tsx | 2 +- .../features/permits/types/PermitCondition.ts | 8 + .../src/features/permits/types/PermitData.ts | 4 +- .../src/features/permits/types/PermitLOA.ts | 41 ++++ .../src/features/settings/apiManager/loa.ts | 131 ++++++++++++ .../apiManager/specialAuthorization.ts | 126 +----------- .../LOA/expired/ExpiredLOAModal.tsx | 2 +- .../LOA/list/LOADownloadCell.tsx | 2 +- .../LOA/list/LOAList.tsx | 2 +- .../LOA/list/LOAListColumnDef.tsx | 2 +- .../LOA/list/LOANumberCell.tsx | 2 +- .../features/settings/helpers/permissions.ts | 1 + frontend/src/features/settings/hooks/LOA.ts | 8 +- .../SpecialAuthorizations.tsx | 2 +- .../src/features/settings/types/LOADetail.ts | 38 ++++ .../features/settings/types/LOAFormData.ts | 2 +- .../settings/types/SpecialAuthorization.ts | 67 +----- 50 files changed, 999 insertions(+), 720 deletions(-) create mode 100644 frontend/src/common/helpers/equality.ts create mode 100644 frontend/src/common/hooks/useMemoizedArray.ts create mode 100644 frontend/src/common/hooks/useMemoizedObject.ts rename frontend/src/features/permits/pages/Application/components/form/{PermitLOA.scss => PermitLOASection.scss} (70%) rename frontend/src/features/permits/pages/Application/components/form/{PermitLOA.tsx => PermitLOASection.tsx} (83%) create mode 100644 frontend/src/features/permits/types/PermitLOA.ts create mode 100644 frontend/src/features/settings/apiManager/loa.ts create mode 100644 frontend/src/features/settings/types/LOADetail.ts diff --git a/frontend/src/common/helpers/equality.ts b/frontend/src/common/helpers/equality.ts new file mode 100644 index 000000000..2a128acc3 --- /dev/null +++ b/frontend/src/common/helpers/equality.ts @@ -0,0 +1,77 @@ +import { Nullable } from "../types/common"; + +/** + * Check if two nullable values are different. + * @param val1 First nullable value to be compared + * @param val2 Second nullable value to be compared + * @returns true when only one of the values are empty, or both are non-empty and different, false otherwise + */ +export const areValuesDifferent = ( + val1?: Nullable, + val2?: Nullable, +): boolean => { + if (!val1 && !val2) return false; // Both empty implicitly means that values are the same + + if ((val1 && !val2) || (!val1 && val2) || (val1 && val2 && val1 !== val2)) { + return true; // Only one empty, or both are non-empty but different means that values are different + } + + return false; // Values are considered equal otherwise +}; + +/** + * Determine whether or not two arrays, each with only unique primitive values, have the same values. + * @param arr1 First array consisting of only non-duplicate primitive values + * @param arr2 Second array consisting of only non-duplicate primitive values + * @returns Whether or not the two arrays have the same values + */ +export const doUniqueArraysHaveSameItems = ( + arr1: T[], + arr2: T[], +) => { + const set1 = new Set(arr1); + const set2 = new Set(arr2); + + for (const val of set1) { + if (!set2.has(val)) return false; + } + + for (const val of set2) { + if (!set1.has(val)) return false; + } + + return true; +}; + +/** + * Determine whether or not two arrays, each with objects of a certain type identifiable by keys, + * have the same objects. + * @param arr1 First array consisting of identifiable objects + * @param arr2 Second array consisting of identifiable objects + * @param key Function that returns the identifier of an object of the given type + * @param equalFn Function that compares equality of two objects of the given type + * @returns Whether or not the two arrays have the same objects + */ +export const doUniqueArraysHaveSameObjects = ( + arr1: T[], + arr2: T[], + key: (item: T) => K, + equalFn: (item1: T, item2: T) => boolean, +) => { + const map1 = new Map(arr1.map(item => [key(item), item])); + const map2 = new Map(arr2.map(item => [key(item), item])); + + for (const [key, item] of map1) { + const itemInOtherMapWithSameKey = map2.get(key); + if (!itemInOtherMapWithSameKey || !equalFn(item, itemInOtherMapWithSameKey)) + return false; + } + + for (const [key, item] of map2) { + const itemInOtherMapWithSameKey = map1.get(key); + if (!itemInOtherMapWithSameKey || !equalFn(item, itemInOtherMapWithSameKey)) + return false; + } + + return true; +}; diff --git a/frontend/src/common/helpers/util.ts b/frontend/src/common/helpers/util.ts index 24b5e0b89..5fea00c4c 100644 --- a/frontend/src/common/helpers/util.ts +++ b/frontend/src/common/helpers/util.ts @@ -122,25 +122,6 @@ export const getDefaultRequiredVal = ( return defaultVals.find((val) => val != null) ?? fallbackDefault; }; -/** - * Check if two nullable values are different. - * @param val1 First nullable value to be compared - * @param val2 Second nullable value to be compared - * @returns boolean value indicating if values are different. - */ -export const areValuesDifferent = ( - val1?: Nullable, - val2?: Nullable, -): boolean => { - if (!val1 && !val2) return false; // both empty === equal - - if ((val1 && !val2) || (!val1 && val2) || (val1 && val2 && val1 !== val2)) { - return true; // one empty, or both non-empty but different === different - } - - return false; // values are equal otherwise -}; - /** * Returns the file name for a file from API response. * @param headers The collection of headers in an API response. @@ -274,27 +255,3 @@ export const setRedirectInSession = (redirectUri: string) => { } } }; - -/** - * Determine whether or not two arrays have the same items. - * @param arr1 First array - * @param arr2 Second array - * @returns Whether or not the two arrays contain the same items - */ -export const areArraysEqual = ( - arr1: T[], - arr2: T[], -) => { - const set1 = new Set(arr1); - const set2 = new Set(arr2); - - for (const val of set1) { - if (!set2.has(val)) return false; - } - - for (const val of set2) { - if (!set1.has(val)) return false; - } - - return true; -}; diff --git a/frontend/src/common/hooks/useMemoizedArray.ts b/frontend/src/common/hooks/useMemoizedArray.ts new file mode 100644 index 000000000..cf6599158 --- /dev/null +++ b/frontend/src/common/hooks/useMemoizedArray.ts @@ -0,0 +1,33 @@ +import { useEffect, useState } from "react"; +import { doUniqueArraysHaveSameObjects } from "../helpers/equality"; + +/** + * Hook that memoizes an array of objects. + * The memoized array only changes when the items in the array change. + * eg. If items === [{a: 1}, {a: 2}], and later [{a: 2}, {a: 1}] is passed in, + * the hook returns the same items [{a: 1}, {a: 2}]. + * @param items Array of objects + * @param key Function that returns identifier for each object + * @param equalFn Function that determines whether or not two objects are equal + * @returns Memoized array of objects + */ +export const useMemoizedArray = ( + items: T[], + key: (item: T) => K, + equalFn: (item1: T, item2: T) => boolean, +) => { + const [arrayItems, setArrayItems] = useState(items); + + useEffect(() => { + if (!doUniqueArraysHaveSameObjects( + arrayItems, + items, + key, + equalFn, + )) { + setArrayItems(items); + } + }, [items]); + + return arrayItems; +}; diff --git a/frontend/src/common/hooks/useMemoizedObject.ts b/frontend/src/common/hooks/useMemoizedObject.ts new file mode 100644 index 000000000..4d4d0725f --- /dev/null +++ b/frontend/src/common/hooks/useMemoizedObject.ts @@ -0,0 +1,26 @@ +import { useEffect, useState } from "react"; + +/** + * Hook that memoizes an object. + * The memoized object only changes when its contents change. + * eg. If obj === {a: 1, b: 2}, and later {b: 2, a: 1} is passed in, + * the hook returns the same obj {a: 1, b: 2}. + * @param obj An object + * @param equalFn Function that determines whether or not two objects are equal + * @returns Memoized object + */ +export const useMemoizedObject = ( + obj: T, + equalFn: (obj1: T, obj2: T) => boolean, +) => { + const [memoizedObj, setMemoizedObj] = useState(obj); + + useEffect(() => { + if (!equalFn(memoizedObj, obj)) { + setMemoizedObj(obj); + } + }, [obj]); + + return memoizedObj; +}; + diff --git a/frontend/src/features/manageVehicles/helpers/vehicleSubtypes.ts b/frontend/src/features/manageVehicles/helpers/vehicleSubtypes.ts index 596fe3766..144302cb1 100644 --- a/frontend/src/features/manageVehicles/helpers/vehicleSubtypes.ts +++ b/frontend/src/features/manageVehicles/helpers/vehicleSubtypes.ts @@ -1,3 +1,10 @@ +import { + BaseVehicle, + PowerUnit, + Trailer, + VEHICLE_TYPES, +} from "../types/Vehicle"; + /** * Determine whether or not a vehicle subtype ic considered to be LCV. * @param subtype Vehicle subtype @@ -12,3 +19,14 @@ export const EMPTY_VEHICLE_SUBTYPE = { type: "", description: "", }; + +export const selectedVehicleSubtype = (vehicle: BaseVehicle) => { + switch (vehicle.vehicleType) { + case VEHICLE_TYPES.POWER_UNIT: + return (vehicle as PowerUnit).powerUnitTypeCode; + case VEHICLE_TYPES.TRAILER: + return (vehicle as Trailer).trailerTypeCode; + default: + return ""; + } +}; diff --git a/frontend/src/features/permits/context/ApplicationFormContext.ts b/frontend/src/features/permits/context/ApplicationFormContext.ts index 835503fde..42a74b64e 100644 --- a/frontend/src/features/permits/context/ApplicationFormContext.ts +++ b/frontend/src/features/permits/context/ApplicationFormContext.ts @@ -2,7 +2,7 @@ import { createContext } from "react"; import { Dayjs } from "dayjs"; import { PermitVehicleDetails } from "../types/PermitVehicleDetails"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; +import { LOADetail } from "../../settings/types/LOADetail"; import { ApplicationFormData } from "../types/application"; import { getDefaultValues } from "../helpers/getDefaultApplicationFormData"; import { DEFAULT_PERMIT_TYPE } from "../types/PermitType"; @@ -10,6 +10,7 @@ import { PermitCondition } from "../types/PermitCondition"; import { PowerUnit, Trailer, VehicleSubType } from "../../manageVehicles/types/Vehicle"; import { Nullable } from "../../../common/types/common"; import { CompanyProfile } from "../../manageProfile/types/manageProfile.d"; +import { PermitLOA } from "../types/PermitLOA"; import { PAST_START_DATE_STATUSES, PastStartDateStatus, @@ -49,7 +50,7 @@ interface ApplicationFormContextType { onToggleSaveVehicle: (saveVehicle: boolean) => void; onSetVehicle: (vehicleDetails: PermitVehicleDetails) => void; onClearVehicle: (saveVehicle: boolean) => void; - onUpdateLOAs: (updatedLOAs: LOADetail[]) => void; + onUpdateLOAs: (updatedLOAs: PermitLOA[]) => void; } export const ApplicationFormContext = createContext({ diff --git a/frontend/src/features/permits/helpers/conditions.ts b/frontend/src/features/permits/helpers/conditions.ts index e52ddc1d9..681c5d263 100644 --- a/frontend/src/features/permits/helpers/conditions.ts +++ b/frontend/src/features/permits/helpers/conditions.ts @@ -1,3 +1,4 @@ +import { isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes"; import { LCV_CONDITION } from "../constants/constants"; import { MANDATORY_TROS_CONDITIONS, TROS_CONDITIONS } from "../constants/tros"; import { MANDATORY_TROW_CONDITIONS, TROW_CONDITIONS } from "../constants/trow"; @@ -83,3 +84,86 @@ export const getDefaultConditions = ( })), ); }; + +/** + * Get updated permit conditions based on LCV designation and selected vehicle subtype. + * @param isLcvDesignated Whether or not the LCV designation is to be used + * @param prevSelectedConditions Previously selected permit conditions + * @param vehicleSubtype Selected vehicle subtype + * @returns Updated permit conditions + */ +export const getUpdatedConditionsForLCV = ( + isLcvDesignated: boolean, + prevSelectedConditions: PermitCondition[], + vehicleSubtype: string, +) => { + if (!isLcvDesignated) { + // If LCV not designated, remove LCV condition + return prevSelectedConditions.filter( + ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition, + ); + } + + // If LCV is designated, and vehicle subtype isn't LCV but conditions have LCV, + // then remove that LCV condition + if ( + !isVehicleSubtypeLCV(vehicleSubtype) + && prevSelectedConditions.some(({ condition }) => condition === LCV_CONDITION.condition) + ) { + return prevSelectedConditions.filter( + ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition, + ); + } + + // If LCV is designated, and vehicle subtype is LCV but conditions don't have LCV, + // then add that LCV condition + if ( + isVehicleSubtypeLCV(vehicleSubtype) + && !prevSelectedConditions.some(({ condition }) => condition === LCV_CONDITION.condition) + ) { + return sortConditions([...prevSelectedConditions, LCV_CONDITION]); + } + + // In other cases, the conditions are valid + return prevSelectedConditions; +}; + +/** + * Get permit condition selection state, including all selected, unselected, and disabled conditions. + * @param permitType Permit type + * @param isLcvDesignated Whether or not the LCV designation is to be used + * @param vehicleSubtype Selected vehicle subtype + * @param prevSelectedConditions Previously selected permit conditions + * @returns Permit condition selection state + */ +export const getPermitConditionSelectionState = ( + permitType: PermitType, + isLcvDesignated: boolean, + vehicleSubtype: string, + prevSelectedConditions: PermitCondition[], +): PermitCondition[] => { + const defaultConditionsForPermitType = getDefaultConditions( + permitType, + isLcvDesignated && isVehicleSubtypeLCV(vehicleSubtype), + ); + + const updatedConditionsInForm = getUpdatedConditionsForLCV( + isLcvDesignated, + prevSelectedConditions, + vehicleSubtype, + ); + + return defaultConditionsForPermitType.map((defaultCondition) => { + // Select all conditions that were previously selected + const existingCondition = updatedConditionsInForm.find( + (c) => c.condition === defaultCondition.condition, + ); + + return { + ...defaultCondition, + checked: existingCondition + ? existingCondition.checked + : defaultCondition.checked, + }; + }); +}; diff --git a/frontend/src/features/permits/helpers/dateSelection.ts b/frontend/src/features/permits/helpers/dateSelection.ts index 68d6a6cec..4472fa941 100644 --- a/frontend/src/features/permits/helpers/dateSelection.ts +++ b/frontend/src/features/permits/helpers/dateSelection.ts @@ -2,6 +2,9 @@ import { Dayjs } from "dayjs"; import { BASE_DAYS_IN_YEAR, TERM_DURATION_INTERVAL_DAYS } from "../constants/constants"; import { PERMIT_TYPES, PermitType } from "../types/PermitType"; +import { getExpiryDate } from "./permitState"; +import { getMostRecentExpiryFromLOAs } from "./permitLOA"; +import { PermitLOA } from "../types/PermitLOA"; import { MAX_TROS_DURATION, MIN_TROS_DURATION, @@ -15,9 +18,6 @@ import { TROW_DURATION_INTERVAL_DAYS, TROW_DURATION_OPTIONS, } from "../constants/trow"; -import { getExpiryDate } from "./permitState"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; -import { getMostRecentExpiryFromLOAs } from "./permitLOA"; /** * Get list of selectable duration options for a given permit type. @@ -94,7 +94,7 @@ export const getAvailableDurationOptions = ( value: number; label: string; }[], - selectedLOAs: LOADetail[], + selectedLOAs: PermitLOA[], startDate: Dayjs, ) => { const mostRecentLOAExpiry = getMostRecentExpiryFromLOAs(selectedLOAs); diff --git a/frontend/src/features/permits/helpers/equality.ts b/frontend/src/features/permits/helpers/equality.ts index 8d384c04b..e1d2ddb0f 100644 --- a/frontend/src/features/permits/helpers/equality.ts +++ b/frontend/src/features/permits/helpers/equality.ts @@ -4,12 +4,13 @@ import { PermitMailingAddress } from "../types/PermitMailingAddress"; import { PermitContactDetails } from "../types/PermitContactDetails"; import { PermitVehicleDetails } from "../types/PermitVehicleDetails"; import { PermitData } from "../types/PermitData"; -import { areLOADetailsEqual, LOADetail } from "../../settings/types/SpecialAuthorization"; import { PermitCondition } from "../types/PermitCondition"; +import { arePermitLOADetailsEqual, PermitLOA } from "../types/PermitLOA"; import { DATE_FORMATS, dayjsToLocalStr, } from "../../../common/helpers/formatDate"; +import { doUniqueArraysHaveSameObjects } from "../../../common/helpers/equality"; /** * Compare whether or not two mailing addresses are equal. @@ -121,8 +122,8 @@ const areVehicleDetailsEqual = ( * @returns true when the selected LOAs are the same, false otherwise */ export const arePermitLOAsEqual = ( - loas1: Nullable, - loas2: Nullable, + loas1: Nullable, + loas2: Nullable, ) => { const isLoas1Empty = !loas1 || loas1.length === 0; const isLoas2Empty = !loas2 || loas2.length === 0; @@ -131,26 +132,12 @@ export const arePermitLOAsEqual = ( if ((isLoas1Empty && !isLoas2Empty) || (!isLoas1Empty && isLoas2Empty)) return false; - const loaMap1 = new Map( - (loas1 as LOADetail[]).map((loa) => [loa.loaNumber, loa]), + return doUniqueArraysHaveSameObjects( + loas1 as PermitLOA[], + loas2 as PermitLOA[], + (loa) => loa.loaNumber, + arePermitLOADetailsEqual, ); - const loaMap2 = new Map( - (loas2 as LOADetail[]).map((loa) => [loa.loaNumber, loa]), - ); - - for (const [loaNumber, loa] of loaMap1) { - if (!areLOADetailsEqual(loa, loaMap2.get(loaNumber))) { - return false; - } - } - - for (const [loaNumber, loa] of loaMap2) { - if (!areLOADetailsEqual(loa, loaMap1.get(loaNumber))) { - return false; - } - } - - return true; }; /** diff --git a/frontend/src/features/permits/helpers/permitLCV.ts b/frontend/src/features/permits/helpers/permitLCV.ts index de37957f6..60237460e 100644 --- a/frontend/src/features/permits/helpers/permitLCV.ts +++ b/frontend/src/features/permits/helpers/permitLCV.ts @@ -1,10 +1,8 @@ import { Nullable } from "../../../common/types/common"; import { isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes"; -import { LCV_CONDITION } from "../constants/constants"; import { Application, ApplicationFormData } from "../types/application"; -import { PermitCondition } from "../types/PermitCondition"; import { PermitVehicleDetails } from "../types/PermitVehicleDetails"; -import { sortConditions } from "./conditions"; +import { getPermitConditionSelectionState } from "./conditions"; import { getDefaultVehicleDetails } from "./permitVehicles"; /** @@ -26,49 +24,6 @@ export const getUpdatedVehicleDetailsForLCV = ( return prevSelectedVehicle; }; -/** - * Get updated permit conditions based on LCV designation and selected vehicle subtype. - * @param isLcvDesignated Whether or not the LCV designation is to be used - * @param prevSelectedConditions Previously selected permit conditions - * @param vehicleSubtype Selected vehicle subtype - * @returns Updated permit conditions - */ -export const getUpdatedConditionsForLCV = ( - isLcvDesignated: boolean, - prevSelectedConditions: PermitCondition[], - vehicleSubtype: string, -) => { - if (!isLcvDesignated) { - // If LCV not designated, remove LCV condition - return prevSelectedConditions.filter( - ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition, - ); - } - - // If LCV is designated, and vehicle subtype isn't LCV but conditions have LCV, - // then remove that LCV condition - if ( - !isVehicleSubtypeLCV(vehicleSubtype) - && prevSelectedConditions.some(({ condition }) => condition === LCV_CONDITION.condition) - ) { - return prevSelectedConditions.filter( - ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition, - ); - } - - // If LCV is designated, and vehicle subtype is LCV but conditions don't have LCV, - // then add that LCV condition - if ( - isVehicleSubtypeLCV(vehicleSubtype) - && !prevSelectedConditions.some(({ condition }) => condition === LCV_CONDITION.condition) - ) { - return sortConditions([...prevSelectedConditions, LCV_CONDITION]); - } - - // In other cases, the conditions are valid - return prevSelectedConditions; -}; - /** * Applying LCV designation to application data. * @param applicationData Existing application data @@ -87,11 +42,12 @@ export const applyLCVToApplicationData = checked); return { ...applicationData, diff --git a/frontend/src/features/permits/helpers/permitLOA.ts b/frontend/src/features/permits/helpers/permitLOA.ts index afb151a0c..c24c11568 100644 --- a/frontend/src/features/permits/helpers/permitLOA.ts +++ b/frontend/src/features/permits/helpers/permitLOA.ts @@ -1,6 +1,6 @@ import dayjs, { Dayjs } from "dayjs"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; +import { LOADetail } from "../../settings/types/LOADetail"; import { PermitType } from "../types/PermitType"; import { getEndOfDate, toLocalDayjs } from "../../../common/helpers/formatDate"; import { Nullable } from "../../../common/types/common"; @@ -9,6 +9,7 @@ import { getDefaultRequiredVal } from "../../../common/helpers/util"; import { PowerUnit, Trailer, VEHICLE_TYPES } from "../../manageVehicles/types/Vehicle"; import { PermitVehicleDetails } from "../types/PermitVehicleDetails"; import { filterVehicles, getDefaultVehicleDetails } from "./permitVehicles"; +import { PermitLOA } from "../types/PermitLOA"; import { durationOptionsForPermitType, getAvailableDurationOptions, @@ -52,7 +53,7 @@ export const filterNonExpiredLOAs = ( * @param loas LOAs with or without expiry dates * @returns The most recent expiry date for all the LOAs, or null if none of the LOAs expire */ -export const getMostRecentExpiryFromLOAs = (loas: LOADetail[]) => { +export const getMostRecentExpiryFromLOAs = (loas: PermitLOA[]) => { const expiringLOAs = loas.filter(loa => Boolean(loa.expiryDate)); if (expiringLOAs.length === 0) return null; @@ -75,7 +76,7 @@ export const getMostRecentExpiryFromLOAs = (loas: LOADetail[]) => { */ export const getUpdatedLOASelection = ( upToDateLOAs: LOADetail[], - prevSelectedLOAs: LOADetail[], + prevSelectedLOAs: PermitLOA[], minPermitExpiryDate: Dayjs, ) => { // Each LOA should only be selected once, but there's a chance that an up-to-date LOA is also a previously selected LOA, @@ -94,7 +95,18 @@ export const getUpdatedLOASelection = ( const isEnabled = !isExpiringBeforeMinPermitExpiry; return { - loa, + loa: { + loaId: loa.loaId, + loaNumber: loa.loaNumber, + companyId: loa.companyId, + startDate: loa.startDate, + expiryDate: loa.expiryDate, + loaPermitType: loa.loaPermitType, + powerUnits: loa.powerUnits, + trailers: loa.trailers, + originalLoaId: loa.originalLoaId, + previousLoaId: loa.previousLoaId, + }, checked: isSelected, disabled: !isEnabled, }; @@ -111,7 +123,7 @@ export const getUpdatedLOASelection = ( * @returns Updated vehicle details and filtered vehicle options */ export const getUpdatedVehicleDetailsForLOAs = ( - selectedLOAs: LOADetail[], + selectedLOAs: PermitLOA[], vehicleOptions: (PowerUnit | Trailer)[], prevSelectedVehicle: PermitVehicleDetails, ineligiblePowerUnitSubtypes: string[], diff --git a/frontend/src/features/permits/helpers/permitVehicles.ts b/frontend/src/features/permits/helpers/permitVehicles.ts index aa667e489..e250c87e0 100644 --- a/frontend/src/features/permits/helpers/permitVehicles.ts +++ b/frontend/src/features/permits/helpers/permitVehicles.ts @@ -1,11 +1,11 @@ import { PERMIT_TYPES, PermitType } from "../types/PermitType"; import { TROW_INELIGIBLE_POWERUNITS, TROW_INELIGIBLE_TRAILERS } from "../constants/trow"; import { TROS_INELIGIBLE_POWERUNITS, TROS_INELIGIBLE_TRAILERS } from "../constants/tros"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; +import { PermitLOA } from "../types/PermitLOA"; import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../common/helpers/util"; import { Nullable } from "../../../common/types/common"; import { PermitVehicleDetails } from "../types/PermitVehicleDetails"; -import { isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes"; +import { EMPTY_VEHICLE_SUBTYPE, isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes"; import { PowerUnit, Trailer, @@ -14,6 +14,7 @@ import { VEHICLE_TYPES, Vehicle, } from "../../manageVehicles/types/Vehicle"; +import { sortVehicleSubTypes } from "./sorter"; export const getIneligiblePowerUnitSubtypes = (permitType: PermitType) => { switch (permitType) { @@ -120,7 +121,7 @@ export const filterVehicles = ( vehicles: Vehicle[], ineligiblePowerUnitSubtypes: string[], ineligibleTrailerSubtypes: string[], - loas: LOADetail[], + loas: PermitLOA[], ) => { const permittedPowerUnitIds = new Set([ ...loas.map(loa => loa.powerUnits) @@ -154,3 +155,67 @@ export const filterVehicles = ( }); }); }; + +/** + * Get vehicle subtype options for given vehicle type. + * @param vehicleType Vehicle type + * @param powerUnitSubtypes Vehicle subtypes for power units + * @param trailerSubtypes Vehicle subtypes for trailers + * @returns Correct vehicle subtype options for vehicle type + */ +export const getSubtypeOptions = ( + vehicleType: string, + powerUnitSubtypes: VehicleSubType[], + trailerSubtypes: VehicleSubType[], +) => { + if (vehicleType === VEHICLE_TYPES.POWER_UNIT) { + return [...powerUnitSubtypes]; + } + if (vehicleType === VEHICLE_TYPES.TRAILER) { + return [...trailerSubtypes]; + } + return [EMPTY_VEHICLE_SUBTYPE]; +}; + +/** + * Get eligible subset of vehicle subtype options given lists of available subtypes and criteria. + * @param powerUnitSubtypes All available power unit subtypes + * @param trailerSubtypes All available trailer subtypes + * @param ineligiblePowerUnitSubtypes List of ineligible power unit subtypes + * @param ineligibleTrailerSubtypes List of ineligible trailer subtypes + * @param allowedLOAPowerUnitSubtypes List of power unit subtypes allowed by LOAs + * @param allowedLOATrailerSubtypes List of trailer subtypes allowed by LOAs + * @param vehicleType Vehicle type + * @returns Eligible subset of vehicle subtype options + */ +export const getEligibleSubtypeOptions = ( + powerUnitSubtypes: VehicleSubType[], + trailerSubtypes: VehicleSubType[], + ineligiblePowerUnitSubtypes: VehicleSubType[], + ineligibleTrailerSubtypes: VehicleSubType[], + allowedLOAPowerUnitSubtypes: string[], + allowedLOATrailerSubtypes: string[], + vehicleType?: string, +) => { + if ( + vehicleType !== VEHICLE_TYPES.POWER_UNIT && + vehicleType !== VEHICLE_TYPES.TRAILER + ) { + return [EMPTY_VEHICLE_SUBTYPE]; + } + + // Sort vehicle subtypes alphabetically + const sortedVehicleSubtypes = sortVehicleSubTypes( + vehicleType, + getSubtypeOptions(vehicleType, powerUnitSubtypes, trailerSubtypes), + ); + + return filterVehicleSubtypes( + sortedVehicleSubtypes, + vehicleType, + ineligiblePowerUnitSubtypes, + ineligibleTrailerSubtypes, + allowedLOAPowerUnitSubtypes, + allowedLOATrailerSubtypes, + ); +}; diff --git a/frontend/src/features/permits/hooks/useApplicationFormContext.ts b/frontend/src/features/permits/hooks/useApplicationFormContext.ts index cb67852d6..6d3ec01a0 100644 --- a/frontend/src/features/permits/hooks/useApplicationFormContext.ts +++ b/frontend/src/features/permits/hooks/useApplicationFormContext.ts @@ -2,11 +2,15 @@ import { useContext } from "react"; import { ApplicationFormContext } from "../context/ApplicationFormContext"; import { usePermitDateSelection } from "./usePermitDateSelection"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; import { usePermitConditions } from "./usePermitConditions"; import { getStartOfDate } from "../../../common/helpers/formatDate"; import { getIneligibleSubtypes } from "../helpers/permitVehicles"; import { usePermitVehicleForLOAs } from "./usePermitVehicleForLOAs"; +import { arePermitLOADetailsEqual, PermitLOA } from "../types/PermitLOA"; +import { useMemoizedArray } from "../../../common/hooks/useMemoizedArray"; +import { getDefaultRequiredVal } from "../../../common/helpers/util"; +import { arePermitConditionEqual } from "../types/PermitCondition"; +import { useMemoizedObject } from "../../../common/hooks/useMemoizedObject"; export const useApplicationFormContext = () => { const { @@ -38,67 +42,146 @@ export const useApplicationFormContext = () => { onUpdateLOAs, } = useContext(ApplicationFormContext); - const permitType = formData.permitType; const { - loas: currentSelectedLOAs, + permitType, + applicationNumber, + permitNumber, + } = formData; + + const { + expiryDate: permitExpiryDate, + loas, permitDuration, startDate: permitStartDate, - commodities: permitConditions, + commodities, vehicleDetails: vehicleFormData, } = formData.permitData; + const createdAt = useMemoizedObject( + createdDateTime, + (dateObj1, dateObj2) => Boolean( + (!dateObj1 && !dateObj2) || (dateObj1 && dateObj2 && dateObj1.isSame(dateObj2)), + ), + ); + + const updatedAt = useMemoizedObject( + updatedDateTime, + (dateObj1, dateObj2) => Boolean( + (!dateObj1 && !dateObj2) || (dateObj1 && dateObj2 && dateObj1.isSame(dateObj2)), + ), + ); + + const startDate = useMemoizedObject( + getStartOfDate(permitStartDate), + (dateObj1, dateObj2) => dateObj1.isSame(dateObj2), + ); + + const expiryDate = useMemoizedObject( + permitExpiryDate, + (dateObj1, dateObj2) => dateObj1.isSame(dateObj2), + ); + + const currentSelectedLOAs = useMemoizedArray( + getDefaultRequiredVal([], loas), + ({ loaNumber }) => loaNumber, + arePermitLOADetailsEqual, + ); + + const permitConditions = useMemoizedArray( + commodities, + ({ condition }) => condition, + arePermitConditionEqual, + ); + // Update duration options and expiry when needed const { availableDurationOptions } = usePermitDateSelection( permitType, - getStartOfDate(permitStartDate), + startDate, durationOptions, - currentSelectedLOAs as LOADetail[], + currentSelectedLOAs as PermitLOA[], permitDuration, onSetDuration, onSetExpiryDate, ); // Update permit conditions when LCV designation or vehicle subtype changes - usePermitConditions( + const { allConditions } = usePermitConditions( + permitType, permitConditions, isLcvDesignated, vehicleFormData.vehicleSubType, onSetConditions, ); + // Get ineligible vehicle subtypes + const ineligibleSubtypes = getIneligibleSubtypes(permitType, isLcvDesignated); + const ineligiblePowerUnitSubtypes = useMemoizedArray( + ineligibleSubtypes.ineligiblePowerUnitSubtypes, + (subtype) => subtype.typeCode, + (subtype1, subtype2) => subtype1.typeCode === subtype2.typeCode, + ); + + const ineligibleTrailerSubtypes = useMemoizedArray( + ineligibleSubtypes.ineligibleTrailerSubtypes, + (subtype) => subtype.typeCode, + (subtype1, subtype2) => subtype1.typeCode === subtype2.typeCode, + ); + // Check to see if vehicle details is still valid after LOA has been deselected + // Also get vehicle subtype options, and whether or not selected vehicle is an LOA vehicle const { - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, - } = getIneligibleSubtypes(permitType, isLcvDesignated); - - const { filteredVehicleOptions } = usePermitVehicleForLOAs( + filteredVehicleOptions, + subtypeOptions, + isSelectedLOAVehicle, + } = usePermitVehicleForLOAs( vehicleFormData, vehicleOptions, - currentSelectedLOAs as LOADetail[], - ineligiblePowerUnitSubtypes.map(({ typeCode }) => typeCode), - ineligibleTrailerSubtypes.map(({ typeCode }) => typeCode), + currentSelectedLOAs as PermitLOA[], + powerUnitSubtypes, + trailerSubtypes, + ineligiblePowerUnitSubtypes, + ineligibleTrailerSubtypes, () => onClearVehicle(Boolean(vehicleFormData.saveVehicle)), ); + const memoizedCompanyLOAs = useMemoizedArray( + companyLOAs, + ({ loaNumber }) => loaNumber, + arePermitLOADetailsEqual, + ); + + const memoizedRevisionHistory = useMemoizedArray( + revisionHistory, + (historyItem) => `${historyItem.permitId}-${historyItem.revisionDateTime}`, + (historyItem1, historyItem2) => + historyItem1.permitId === historyItem2.permitId + && historyItem1.revisionDateTime === historyItem2.revisionDateTime + && historyItem1.name === historyItem2.name + && historyItem1.comment === historyItem2.comment, + ); + return { initialFormData, - formData, + permitType, + applicationNumber, + permitNumber, + startDate, + expiryDate, + currentSelectedLOAs, + vehicleFormData, + allConditions, availableDurationOptions, - powerUnitSubtypes, - trailerSubtypes, - isLcvDesignated, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, filteredVehicleOptions, + subtypeOptions, + isSelectedLOAVehicle, feature, companyInfo, isAmendAction, - createdDateTime, - updatedDateTime, + createdDateTime: createdAt, + updatedDateTime: updatedAt, pastStartDateStatus, - companyLOAs, - revisionHistory, + companyLOAs: memoizedCompanyLOAs, + revisionHistory: memoizedRevisionHistory, onLeave, onSave, onCancel, diff --git a/frontend/src/features/permits/hooks/useInitApplicationFormData.ts b/frontend/src/features/permits/hooks/useInitApplicationFormData.ts index 4c60e4147..1b410ff0b 100644 --- a/frontend/src/features/permits/hooks/useInitApplicationFormData.ts +++ b/frontend/src/features/permits/hooks/useInitApplicationFormData.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; import dayjs, { Dayjs } from "dayjs"; @@ -7,7 +7,7 @@ import { BCeIDUserDetailContext } from "../../../common/authentication/OnRouteBC import { CompanyProfile } from "../../manageProfile/types/manageProfile"; import { Nullable } from "../../../common/types/common"; import { PermitType } from "../types/PermitType"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; +import { LOADetail } from "../../settings/types/LOADetail"; import { applyUpToDateLOAsToApplication } from "../helpers/permitLOA"; import { getDefaultValues } from "../helpers/getDefaultApplicationFormData"; import { applyLCVToApplicationData } from "../helpers/permitLCV"; @@ -15,6 +15,7 @@ import { PowerUnit, Trailer } from "../../manageVehicles/types/Vehicle"; import { getIneligibleSubtypes } from "../helpers/permitVehicles"; import { PermitCondition } from "../types/PermitCondition"; import { EMPTY_VEHICLE_DETAILS, PermitVehicleDetails } from "../types/PermitVehicleDetails"; +import { PermitLOA } from "../types/PermitLOA"; /** * Custom hook for populating the form using fetched application data, as well as current company id and user details. @@ -30,7 +31,7 @@ import { EMPTY_VEHICLE_DETAILS, PermitVehicleDetails } from "../types/PermitVehi export const useInitApplicationFormData = ( permitType: PermitType, isLcvDesignated: boolean, - loas: LOADetail[], + companyLOAs: LOADetail[], inventoryVehicles: (PowerUnit | Trailer)[], companyInfo: Nullable, applicationData?: Nullable, @@ -56,7 +57,7 @@ export const useInitApplicationFormData = ( ), isLcvDesignated, ), - loas, + companyLOAs, inventoryVehicles, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, @@ -67,7 +68,7 @@ export const useInitApplicationFormData = ( applicationData, userDetails, isLcvDesignated, - loas, + companyLOAs, inventoryVehicles, ]); @@ -85,38 +86,38 @@ export const useInitApplicationFormData = ( reset(initialFormData); }, [initialFormData]); - const onSetDuration = (duration: number) => { + const onSetDuration = useCallback((duration: number) => { setValue("permitData.permitDuration", duration); - }; + }, [setValue]); - const onSetExpiryDate = (expiry: Dayjs) => { + const onSetExpiryDate = useCallback((expiry: Dayjs) => { setValue("permitData.expiryDate", dayjs(expiry)); - }; + }, [setValue]); - const onSetConditions = (conditions: PermitCondition[]) => { + const onSetConditions = useCallback((conditions: PermitCondition[]) => { setValue("permitData.commodities", [...conditions]); - }; + }, [setValue]); - const onToggleSaveVehicle = (saveVehicle: boolean) => { + const onToggleSaveVehicle = useCallback((saveVehicle: boolean) => { setValue("permitData.vehicleDetails.saveVehicle", saveVehicle); - }; + }, [setValue]); - const onSetVehicle = (vehicleDetails: PermitVehicleDetails) => { + const onSetVehicle = useCallback((vehicleDetails: PermitVehicleDetails) => { setValue("permitData.vehicleDetails", { ...vehicleDetails, }); - }; + }, [setValue]); - const onClearVehicle = (saveVehicle: boolean) => { + const onClearVehicle = useCallback((saveVehicle: boolean) => { setValue("permitData.vehicleDetails", { ...EMPTY_VEHICLE_DETAILS, saveVehicle, }); - }; + }, [setValue]); - const onUpdateLOAs = (updatedLOAs: LOADetail[]) => { + const onUpdateLOAs = useCallback((updatedLOAs: PermitLOA[]) => { setValue("permitData.loas", updatedLOAs); - }; + }, [setValue]); return { initialFormData, diff --git a/frontend/src/features/permits/hooks/usePermitConditions.ts b/frontend/src/features/permits/hooks/usePermitConditions.ts index 2d98456d9..0b3786fce 100644 --- a/frontend/src/features/permits/hooks/usePermitConditions.ts +++ b/frontend/src/features/permits/hooks/usePermitConditions.ts @@ -1,34 +1,46 @@ -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; -import { areArraysEqual } from "../../../common/helpers/util"; import { PermitCondition } from "../types/PermitCondition"; -import { getUpdatedConditionsForLCV } from "../helpers/permitLCV"; +import { doUniqueArraysHaveSameItems } from "../../../common/helpers/equality"; +import { PermitType } from "../types/PermitType"; +import { getPermitConditionSelectionState } from "../helpers/conditions"; export const usePermitConditions = ( + permitType: PermitType, selectedConditions: PermitCondition[], isLcvDesignated: boolean, vehicleSubtype: string, onSetConditions: (conditions: PermitCondition[]) => void, ) => { - // If conditions were changed as a result of LCV or vehicle subtype, update permit conditions - const updatedConditions = getUpdatedConditionsForLCV( + // All possible conditions to be used for conditions table, including non-selected ones + const allConditions = useMemo(() => { + return getPermitConditionSelectionState( + permitType, + isLcvDesignated, + vehicleSubtype, + selectedConditions, + ); + }, [ + permitType, isLcvDesignated, - selectedConditions, vehicleSubtype, - ); + selectedConditions, + ]); + + const updatedConditions = allConditions + .filter(({ checked }) => checked); useEffect(() => { - if (!areArraysEqual( + if (!doUniqueArraysHaveSameItems( updatedConditions.map(({ condition }) => condition), - selectedConditions.map(({ condition }: PermitCondition) => condition), + selectedConditions.map(({ condition }) => condition), )) { onSetConditions(updatedConditions); } }, [ updatedConditions, selectedConditions, - onSetConditions, ]); - return { updatedConditions }; + return { allConditions }; }; diff --git a/frontend/src/features/permits/hooks/usePermitDateSelection.ts b/frontend/src/features/permits/hooks/usePermitDateSelection.ts index ca89a2f08..c45462ddb 100644 --- a/frontend/src/features/permits/hooks/usePermitDateSelection.ts +++ b/frontend/src/features/permits/hooks/usePermitDateSelection.ts @@ -2,9 +2,10 @@ import { useEffect } from "react"; import { Dayjs } from "dayjs"; import { getExpiryDate } from "../helpers/permitState"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; import { PermitType } from "../types/PermitType"; import { getAvailableDurationOptions, handleUpdateDurationIfNeeded } from "../helpers/dateSelection"; +import { PermitLOA } from "../types/PermitLOA"; +import { useMemoizedArray } from "../../../common/hooks/useMemoizedArray"; /** * Hook that manages permit date selection based on changing permit data. @@ -22,16 +23,20 @@ export const usePermitDateSelection = ( value: number; label: string; }[], - selectedLOAs: LOADetail[], + selectedLOAs: PermitLOA[], selectedDuration: number, onSetDuration: (duration: number) => void, onSetExpiryDate: (expiry: Dayjs) => void, ) => { // Limit permit duration options based on selected LOAs - const availableDurationOptions = getAvailableDurationOptions( - durationOptions, - selectedLOAs, - startDate, + const availableDurationOptions = useMemoizedArray( + getAvailableDurationOptions( + durationOptions, + selectedLOAs, + startDate, + ), + (option) => option.value, + (option1, option2) => option1.value === option2.value, ); // If duration options change, check if the current permit duration is still selectable @@ -45,7 +50,6 @@ export const usePermitDateSelection = ( onSetDuration(updatedDuration); }, [ updatedDuration, - onSetDuration, ]); const expiryDate = getExpiryDate(startDate, selectedDuration); @@ -53,7 +57,6 @@ export const usePermitDateSelection = ( onSetExpiryDate(expiryDate); }, [ expiryDate, - onSetExpiryDate, ]); return { availableDurationOptions }; diff --git a/frontend/src/features/permits/hooks/usePermitVehicleForLOAs.ts b/frontend/src/features/permits/hooks/usePermitVehicleForLOAs.ts index 082f1a1b5..a6ac81b82 100644 --- a/frontend/src/features/permits/hooks/usePermitVehicleForLOAs.ts +++ b/frontend/src/features/permits/hooks/usePermitVehicleForLOAs.ts @@ -1,29 +1,45 @@ -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; import { PermitVehicleDetails } from "../types/PermitVehicleDetails"; -import { PowerUnit, Trailer } from "../../manageVehicles/types/Vehicle"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; import { getUpdatedVehicleDetailsForLOAs } from "../helpers/permitLOA"; +import { PermitLOA } from "../types/PermitLOA"; +import { getEligibleSubtypeOptions } from "../helpers/permitVehicles"; +import { + PowerUnit, + Trailer, + VEHICLE_TYPES, + VehicleSubType, +} from "../../manageVehicles/types/Vehicle"; export const usePermitVehicleForLOAs = ( vehicleFormData: PermitVehicleDetails, vehicleOptions: (PowerUnit | Trailer)[], - selectedLOAs: LOADetail[], - ineligiblePowerUnitSubtypes: string[], - ineligibleTrailerSubtypes: string[], + selectedLOAs: PermitLOA[], + powerUnitSubtypes: VehicleSubType[], + trailerSubtypes: VehicleSubType[], + ineligiblePowerUnitSubtypes: VehicleSubType[], + ineligibleTrailerSubtypes: VehicleSubType[], onClearVehicle: () => void, ) => { // Check to see if vehicle details is still valid after LOA has been deselected const { - filteredVehicleOptions, updatedVehicle, - } = getUpdatedVehicleDetailsForLOAs( + filteredVehicleOptions, + } = useMemo(() => { + return getUpdatedVehicleDetailsForLOAs( + selectedLOAs, + vehicleOptions, + vehicleFormData, + ineligiblePowerUnitSubtypes.map(({ typeCode }) => typeCode), + ineligibleTrailerSubtypes.map(({ typeCode }) => typeCode), + ); + }, [ selectedLOAs, vehicleOptions, vehicleFormData, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, - ); + ]); const vehicleIdInForm = vehicleFormData.vehicleId; const updatedVehicleId = updatedVehicle.vehicleId; @@ -35,10 +51,87 @@ export const usePermitVehicleForLOAs = ( }, [ vehicleIdInForm, updatedVehicleId, - onClearVehicle, + ]); + + // Get vehicle subtypes that are allowed by LOAs + const vehicleType = vehicleFormData.vehicleType; + const { + subtypeOptions, + isSelectedLOAVehicle, + } = useMemo(() => { + const permittedLOAPowerUnitIds = new Set([ + ...selectedLOAs.map(loa => loa.powerUnits) + .reduce((prevPowerUnits, currPowerUnits) => [ + ...prevPowerUnits, + ...currPowerUnits, + ], []), + ]); + + const permittedLOATrailerIds = new Set([ + ...selectedLOAs.map(loa => loa.trailers) + .reduce((prevTrailers, currTrailers) => [ + ...prevTrailers, + ...currTrailers, + ], []), + ]); + + // Try to find all of the unfiltered vehicles in the inventory, and get a list of their subtypes + // as some of these unfiltered subtypes can potentially be used by a selected LOA + const powerUnitsInInventory = vehicleOptions + .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.POWER_UNIT) as PowerUnit[]; + + const trailersInInventory = vehicleOptions + .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.TRAILER) as Trailer[]; + + const permittedLOAPowerUnitSubtypes = powerUnitsInInventory + .filter(powerUnit => permittedLOAPowerUnitIds.has(powerUnit.powerUnitId as string)) + .map(powerUnit => powerUnit.powerUnitTypeCode); + + const permittedLOATrailerSubtypes = trailersInInventory + .filter(trailer => permittedLOATrailerIds.has(trailer.trailerId as string)) + .map(trailer => trailer.trailerTypeCode); + + // Check if selected vehicle is an LOA vehicle + const isSelectedLOAVehicle = Boolean(vehicleIdInForm) + && ( + permittedLOAPowerUnitIds.has(vehicleIdInForm as string) + || permittedLOATrailerIds.has(vehicleIdInForm as string) + ) + && ( + powerUnitsInInventory.map(powerUnit => powerUnit.powerUnitId) + .includes(vehicleIdInForm as string) + || trailersInInventory.map(trailer => trailer.trailerId) + .includes(vehicleIdInForm as string) + ); + + const subtypeOptions = getEligibleSubtypeOptions( + powerUnitSubtypes, + trailerSubtypes, + ineligiblePowerUnitSubtypes, + ineligibleTrailerSubtypes, + permittedLOAPowerUnitSubtypes, + permittedLOATrailerSubtypes, + vehicleType, + ); + + return { + subtypeOptions, + isSelectedLOAVehicle, + }; + }, [ + selectedLOAs, + vehicleOptions, + vehicleType, + vehicleIdInForm, + powerUnitSubtypes, + trailerSubtypes, + ineligiblePowerUnitSubtypes, + ineligibleTrailerSubtypes, ]); return { filteredVehicleOptions, + subtypeOptions, + isSelectedLOAVehicle, }; }; diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx index 4379cda5b..5e4a44f4b 100644 --- a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx +++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx @@ -21,6 +21,7 @@ import { getDatetimes } from "./helpers/getDatetimes"; import { PAST_START_DATE_STATUSES } from "../../../../../common/components/form/subFormComponents/CustomDatePicker"; import { useFetchLOAs } from "../../../../settings/hooks/LOA"; import { useFetchSpecialAuthorizations } from "../../../../settings/hooks/specialAuthorizations"; +import { filterLOAsForPermitType, filterNonExpiredLOAs } from "../../../helpers/permitLOA"; import { dayjsToUtcStr, nowUtc, @@ -55,6 +56,11 @@ export const AmendPermitForm = () => { const navigate = useNavigate(); const { data: activeLOAs } = useFetchLOAs(companyId, false); + const companyLOAs = useMemo(() => getDefaultRequiredVal( + [], + activeLOAs, + ), [activeLOAs]); + const { data: companyInfo } = useCompanyInfoDetailsQuery(companyId); const { data: specialAuthorizations } = useFetchSpecialAuthorizations(companyId); const isLcvDesignated = Boolean(specialAuthorizations?.isLcvAllowed); @@ -80,7 +86,7 @@ export const AmendPermitForm = () => { } = useAmendPermitForm( currentStepIndex === 0, isLcvDesignated, - getDefaultRequiredVal([], activeLOAs), + companyLOAs, vehicleOptions, companyInfo, permit, @@ -92,8 +98,16 @@ export const AmendPermitForm = () => { permit, ); - const applicableLOAs = getDefaultRequiredVal([], activeLOAs) - .filter(loa => loa.loaPermitType.includes(formData.permitType)); + // Applicable LOAs must be: + // 1. Applicable for the current permit type + // 2. Have expiry date that is on or after the start date for an application + const applicableLOAs = filterNonExpiredLOAs( + filterLOAsForPermitType( + companyLOAs, + formData.permitType, + ), + formData.permitData.startDate, + ); const amendPermitMutation = useAmendPermit(companyId); const modifyAmendmentMutation = useModifyAmendmentApplication(); diff --git a/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts b/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts index 275194cde..6cb6abc82 100644 --- a/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts +++ b/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; import dayjs, { Dayjs } from "dayjs"; @@ -10,20 +10,21 @@ import { CompanyProfile } from "../../../../manageProfile/types/manageProfile"; import { applyLCVToApplicationData } from "../../../helpers/permitLCV"; import { PermitCondition } from "../../../types/PermitCondition"; import { EMPTY_VEHICLE_DETAILS, PermitVehicleDetails } from "../../../types/PermitVehicleDetails"; -import { LOADetail } from "../../../../settings/types/SpecialAuthorization"; +import { LOADetail } from "../../../../settings/types/LOADetail"; import { getIneligibleSubtypes } from "../../../helpers/permitVehicles"; +import { applyUpToDateLOAsToApplication } from "../../../helpers/permitLOA"; +import { PowerUnit, Trailer } from "../../../../manageVehicles/types/Vehicle"; +import { PermitLOA } from "../../../types/PermitLOA"; import { AmendPermitFormData, getDefaultFormDataFromApplication, getDefaultFormDataFromPermit, } from "../types/AmendPermitFormData"; -import { applyUpToDateLOAsToApplication } from "../../../helpers/permitLOA"; -import { PowerUnit, Trailer } from "../../../../manageVehicles/types/Vehicle"; export const useAmendPermitForm = ( repopulateFormData: boolean, isLcvDesignated: boolean, - loas: LOADetail[], + companyLOAs: LOADetail[], inventoryVehicles: (PowerUnit | Trailer)[], companyInfo: Nullable, permit?: Nullable, @@ -51,7 +52,7 @@ export const useAmendPermitForm = ( ), isLcvDesignated, ), - loas, + companyLOAs, inventoryVehicles, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, @@ -87,7 +88,7 @@ export const useAmendPermitForm = ( defaultPermitFormData, isLcvDesignated, ), - loas, + companyLOAs, inventoryVehicles, ineligiblePowerUnitSubtypes, ineligibleTrailerSubtypes, @@ -98,7 +99,7 @@ export const useAmendPermitForm = ( repopulateFormData, companyInfo, isLcvDesignated, - loas, + companyLOAs, inventoryVehicles, ]); @@ -115,38 +116,38 @@ export const useAmendPermitForm = ( reset(defaultFormData); }, [defaultFormData]); - const onSetDuration = (duration: number) => { + const onSetDuration = useCallback((duration: number) => { setValue("permitData.permitDuration", duration); - }; + }, [setValue]); - const onSetExpiryDate = (expiry: Dayjs) => { + const onSetExpiryDate = useCallback((expiry: Dayjs) => { setValue("permitData.expiryDate", dayjs(expiry)); - }; + }, [setValue]); - const onSetConditions = (conditions: PermitCondition[]) => { + const onSetConditions = useCallback((conditions: PermitCondition[]) => { setValue("permitData.commodities", [...conditions]); - }; + }, [setValue]); - const onToggleSaveVehicle = (saveVehicle: boolean) => { + const onToggleSaveVehicle = useCallback((saveVehicle: boolean) => { setValue("permitData.vehicleDetails.saveVehicle", saveVehicle); - }; + }, [setValue]); - const onSetVehicle = (vehicleDetails: PermitVehicleDetails) => { + const onSetVehicle = useCallback((vehicleDetails: PermitVehicleDetails) => { setValue("permitData.vehicleDetails", { ...vehicleDetails, }); - }; + }, [setValue]); - const onClearVehicle = (saveVehicle: boolean) => { + const onClearVehicle = useCallback((saveVehicle: boolean) => { setValue("permitData.vehicleDetails", { ...EMPTY_VEHICLE_DETAILS, saveVehicle, }); - }; + }, [setValue]); - const onUpdateLOAs = (updatedLOAs: LOADetail[]) => { + const onUpdateLOAs = useCallback((updatedLOAs: PermitLOA[]) => { setValue("permitData.loas", updatedLOAs); - }; + }, [setValue]); return { initialFormData: defaultFormData, diff --git a/frontend/src/features/permits/pages/Application/ApplicationForm.tsx b/frontend/src/features/permits/pages/Application/ApplicationForm.tsx index c5d9b48d6..4e91f7dad 100644 --- a/frontend/src/features/permits/pages/Application/ApplicationForm.tsx +++ b/frontend/src/features/permits/pages/Application/ApplicationForm.tsx @@ -68,6 +68,11 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => { ); const { data: activeLOAs } = useFetchLOAs(companyId, false); + const companyLOAs = useMemo(() => getDefaultRequiredVal( + [], + activeLOAs, + ), [activeLOAs]); + const { data: specialAuthorizations } = useFetchSpecialAuthorizations(companyId); const isLcvDesignated = Boolean(specialAuthorizations?.isLcvAllowed); @@ -97,7 +102,7 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => { } = useInitApplicationFormData( permitType, isLcvDesignated, - getDefaultRequiredVal([], activeLOAs), + companyLOAs, vehicleOptions, companyInfo, applicationContext?.applicationData, @@ -109,7 +114,7 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => { // 2. Have expiry date that is on or after the start date for an application const applicableLOAs = filterNonExpiredLOAs( filterLOAsForPermitType( - getDefaultRequiredVal([], activeLOAs), + companyLOAs, permitType, ), currentFormData.permitData.startDate, diff --git a/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx b/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx index 3cb90f67d..1e0f799d8 100644 --- a/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx @@ -12,46 +12,34 @@ import { import "./ConditionsTable.scss"; import { CustomExternalLink } from "../../../../../../common/components/links/CustomExternalLink"; -import { PermitType } from "../../../../types/PermitType"; -import { getDefaultConditions } from "../../../../helpers/conditions"; import { PermitCondition } from "../../../../types/PermitCondition"; export const ConditionsTable = ({ - conditionsInPermit, - permitType, - includeLcvCondition = false, + allConditions, onSetConditions, }: { - conditionsInPermit: PermitCondition[]; - permitType: PermitType; - includeLcvCondition?: boolean; + allConditions: PermitCondition[]; onSetConditions: (conditions: PermitCondition[]) => void; }) => { - const defaultConditions = getDefaultConditions(permitType, includeLcvCondition); - const allConditions = defaultConditions.map((defaultCondition) => { - // Application exists at this point, thus select all conditions that were selected in the application - const existingCondition = conditionsInPermit.find( - (c) => c.condition === defaultCondition.condition, - ); - - return { - ...defaultCondition, - checked: existingCondition - ? existingCondition.checked - : defaultCondition.checked, - }; - }); - const handleSelect = (checkedCondition: string) => { - const updatedConditions = allConditions.map((condition) => { - if (condition.condition === checkedCondition) { - condition.checked = !condition.checked; - } - return condition; - }).filter(condition => condition.checked); + const conditionInTable = allConditions.find(({ condition }) => condition === checkedCondition); + if (!conditionInTable || conditionInTable.disabled) return; - onSetConditions(updatedConditions); - } + const isConditionChecked = Boolean(conditionInTable.checked); + if (isConditionChecked) { + onSetConditions( + allConditions.filter(({ condition, checked }) => checked && condition !== checkedCondition), + ); + } else { + onSetConditions([ + ...allConditions.filter(({ checked }) => checked), + { + ...conditionInTable, + checked: true, + }, + ]); + } + }; return ( diff --git a/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx b/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx index 879b42739..ce6d0dd6d 100644 --- a/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx @@ -11,12 +11,12 @@ import { } from "@mui/material"; import "./LOATable.scss"; -import { LOADetail } from "../../../../../settings/types/SpecialAuthorization"; import { applyWhenNotNullable } from "../../../../../../common/helpers/util"; import { DATE_FORMATS, toLocal } from "../../../../../../common/helpers/formatDate"; +import { PermitLOA } from "../../../../types/PermitLOA"; interface SelectableLOA { - loa: LOADetail; + loa: PermitLOA; checked: boolean; disabled: boolean; } diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitDetails.tsx b/frontend/src/features/permits/pages/Application/components/form/PermitDetails.tsx index 6c9da8679..b161b6b8c 100644 --- a/frontend/src/features/permits/pages/Application/components/form/PermitDetails.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/PermitDetails.tsx @@ -11,7 +11,6 @@ import { requiredMessage } from "../../../../../../common/helpers/validationMess import { ONROUTE_WEBPAGE_LINKS } from "../../../../../../routes/constants"; import { CustomExternalLink } from "../../../../../../common/components/links/CustomExternalLink"; import { BANNER_MESSAGES } from "../../../../../../common/constants/bannerMessages"; -import { PermitType } from "../../../../types/PermitType"; import { PermitCondition } from "../../../../types/PermitCondition"; import { DATE_FORMATS } from "../../../../../../common/helpers/formatDate"; import { @@ -27,25 +26,21 @@ import { export const PermitDetails = ({ feature, expiryDate, - conditionsInPermit, + allConditions, durationOptions, disableStartDate, - permitType, pastStartDateStatus, - includeLcvCondition, onSetConditions, }: { feature: string; expiryDate: Dayjs; - conditionsInPermit: PermitCondition[]; + allConditions: PermitCondition[]; durationOptions: { value: number; label: string; }[]; disableStartDate: boolean; - permitType: PermitType; pastStartDateStatus: PastStartDateStatus; - includeLcvCondition?: boolean; onSetConditions: (conditions: PermitCondition[]) => void; }) => { const formattedExpiryDate = dayjs(expiryDate).format(DATE_FORMATS.SHORT); @@ -131,9 +126,7 @@ export const PermitDetails = ({ />
diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx b/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx index 245f1315b..c51f818fc 100644 --- a/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx @@ -6,24 +6,25 @@ import { ApplicationDetails } from "../../../../components/form/ApplicationDetai import { ContactDetails } from "../../../../components/form/ContactDetails"; import { PermitDetails } from "./PermitDetails"; import { VehicleDetails } from "./VehicleDetails/VehicleDetails"; -import { PermitLOA } from "./PermitLOA"; -import { LOADetail } from "../../../../../settings/types/SpecialAuthorization"; -import { isVehicleSubtypeLCV } from "../../../../../manageVehicles/helpers/vehicleSubtypes"; -import { getStartOfDate } from "../../../../../../common/helpers/formatDate"; +import { PermitLOASection } from "./PermitLOASection"; import { useApplicationFormContext } from "../../../../hooks/useApplicationFormContext"; import { AmendReason } from "../../../Amend/components/form/AmendReason"; import { AmendRevisionHistory } from "../../../Amend/components/form/AmendRevisionHistory"; export const PermitForm = () => { const { - formData, + permitType, + applicationNumber, + permitNumber, + startDate, + expiryDate, + currentSelectedLOAs, + vehicleFormData, + allConditions, availableDurationOptions, - powerUnitSubtypes, - trailerSubtypes, - isLcvDesignated, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, filteredVehicleOptions, + subtypeOptions, + isSelectedLOAVehicle, feature, companyInfo, isAmendAction, @@ -43,15 +44,6 @@ export const PermitForm = () => { onUpdateLOAs, } = useApplicationFormContext(); - const permitType = formData.permitType; - const applicationNumber = formData.applicationNumber; - const permitNumber = formData.permitNumber; - const startDate = getStartOfDate(formData.permitData.startDate); - const expiryDate = formData.permitData.expiryDate; - const permitConditions = formData.permitData.commodities; - const vehicleFormData = formData.permitData.vehicleDetails; - const currentSelectedLOAs = formData.permitData.loas as LOADetail[]; - return ( @@ -70,7 +62,7 @@ export const PermitForm = () => { - { @@ -97,11 +84,8 @@ export const PermitForm = () => { feature={feature} vehicleFormData={vehicleFormData} vehicleOptions={filteredVehicleOptions} - powerUnitSubtypes={powerUnitSubtypes} - trailerSubtypes={trailerSubtypes} - ineligiblePowerUnitSubtypes={ineligiblePowerUnitSubtypes} - ineligibleTrailerSubtypes={ineligibleTrailerSubtypes} - selectedLOAs={currentSelectedLOAs} + subtypeOptions={subtypeOptions} + isSelectedLOAVehicle={isSelectedLOAVehicle} onSetSaveVehicle={onToggleSaveVehicle} onSetVehicle={onSetVehicle} onClearVehicle={onClearVehicle} diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitLOA.scss b/frontend/src/features/permits/pages/Application/components/form/PermitLOASection.scss similarity index 70% rename from frontend/src/features/permits/pages/Application/components/form/PermitLOA.scss rename to frontend/src/features/permits/pages/Application/components/form/PermitLOASection.scss index dea8fc6f2..f5aa24d5a 100644 --- a/frontend/src/features/permits/pages/Application/components/form/PermitLOA.scss +++ b/frontend/src/features/permits/pages/Application/components/form/PermitLOASection.scss @@ -1,10 +1,10 @@ @use "../../../../../../themes/orbcStyles"; -@include orbcStyles.permit-main-box-style(".permit-loa"); -@include orbcStyles.permit-left-box-style(".permit-loa__header"); -@include orbcStyles.permit-right-box-style(".permit-loa__body"); +@include orbcStyles.permit-main-box-style(".permit-loa-section"); +@include orbcStyles.permit-left-box-style(".permit-loa-section__header"); +@include orbcStyles.permit-right-box-style(".permit-loa-section__body"); -.permit-loa { +.permit-loa-section { & &__header { h3 { padding-top: 1rem; diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitLOA.tsx b/frontend/src/features/permits/pages/Application/components/form/PermitLOASection.tsx similarity index 83% rename from frontend/src/features/permits/pages/Application/components/form/PermitLOA.tsx rename to frontend/src/features/permits/pages/Application/components/form/PermitLOASection.tsx index 04d79e251..b99c1177e 100644 --- a/frontend/src/features/permits/pages/Application/components/form/PermitLOA.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/PermitLOASection.tsx @@ -2,17 +2,18 @@ import { useEffect, useMemo } from "react"; import { Dayjs } from "dayjs"; import { Box, Typography } from "@mui/material"; -import "./PermitLOA.scss"; +import "./PermitLOASection.scss"; import { InfoBcGovBanner } from "../../../../../../common/components/banners/InfoBcGovBanner"; import { BANNER_MESSAGES } from "../../../../../../common/constants/bannerMessages"; -import { LOADetail } from "../../../../../settings/types/SpecialAuthorization"; +import { LOADetail } from "../../../../../settings/types/LOADetail"; import { LOATable } from "./LOATable"; import { PermitType } from "../../../../types/PermitType"; import { getMinPermitExpiryDate } from "../../../../helpers/dateSelection"; -import { areArraysEqual } from "../../../../../../common/helpers/util"; import { getUpdatedLOASelection } from "../../../../helpers/permitLOA"; +import { doUniqueArraysHaveSameItems } from "../../../../../../common/helpers/equality"; +import { PermitLOA } from "../../../../types/PermitLOA"; -export const PermitLOA = ({ +export const PermitLOASection = ({ permitType, startDate, selectedLOAs, @@ -21,9 +22,9 @@ export const PermitLOA = ({ }: { permitType: PermitType; startDate: Dayjs; - selectedLOAs: LOADetail[]; + selectedLOAs: PermitLOA[]; companyLOAs: LOADetail[]; - onUpdateLOAs: (updatedLOAs: LOADetail[]) => void, + onUpdateLOAs: (updatedLOAs: PermitLOA[]) => void, }) => { const minPermitExpiryDate = getMinPermitExpiryDate(permitType, startDate); @@ -48,7 +49,7 @@ export const PermitLOA = ({ useEffect(() => { const selectedNumbersInTable = selectedLOAsInTable.map(loa => loa.loaNumber); - if (!areArraysEqual(selectedLOANumbers, selectedNumbersInTable)) { + if (!doUniqueArraysHaveSameItems(selectedLOANumbers, selectedNumbersInTable)) { onUpdateLOAs([...selectedLOAsInTable]); } }, [selectedLOANumbers, selectedLOAsInTable]); @@ -71,14 +72,14 @@ export const PermitLOA = ({ }; return ( - - + + Letter of Authorization (LOA) - +
Select the relevant LOA(s) (optional) diff --git a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx index 1e0667ece..c1da9da1c 100644 --- a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx @@ -17,19 +17,15 @@ import { CustomFormComponent } from "../../../../../../../common/components/form import { InfoBcGovBanner } from "../../../../../../../common/components/banners/InfoBcGovBanner"; import { mapToVehicleObjectById } from "../../../../../helpers/mappers"; import { getDefaultRequiredVal } from "../../../../../../../common/helpers/util"; -import { sortVehicleSubTypes } from "../../../../../helpers/sorter"; -import { filterVehicleSubtypes } from "../../../../../helpers/permitVehicles"; import { CustomInputHTMLAttributes } from "../../../../../../../common/types/formElements"; import { SelectUnitOrPlate } from "./customFields/SelectUnitOrPlate"; import { SelectVehicleDropdown } from "./customFields/SelectVehicleDropdown"; import { BANNER_MESSAGES } from "../../../../../../../common/constants/bannerMessages"; import { PermitVehicleDetails } from "../../../../../types/PermitVehicleDetails"; -import { EMPTY_VEHICLE_SUBTYPE } from "../../../../../../manageVehicles/helpers/vehicleSubtypes"; -import { LOADetail } from "../../../../../../settings/types/SpecialAuthorization"; +import { selectedVehicleSubtype } from "../../../../../../manageVehicles/helpers/vehicleSubtypes"; import { PowerUnit, Trailer, - BaseVehicle, VehicleSubType, VEHICLE_TYPES, Vehicle, @@ -51,74 +47,12 @@ import { requiredMessage, } from "../../../../../../../common/helpers/validationMessages"; -const selectedVehicleSubtype = (vehicle: BaseVehicle) => { - switch (vehicle.vehicleType) { - case VEHICLE_TYPES.POWER_UNIT: - return (vehicle as PowerUnit).powerUnitTypeCode; - case VEHICLE_TYPES.TRAILER: - return (vehicle as Trailer).trailerTypeCode; - default: - return ""; - } -}; - -// Returns correct subtype options based on vehicle type -const getSubtypeOptions = ( - vehicleType: string, - powerUnitSubtypes: VehicleSubType[], - trailerSubtypes: VehicleSubType[], -) => { - if (vehicleType === VEHICLE_TYPES.POWER_UNIT) { - return [...powerUnitSubtypes]; - } - if (vehicleType === VEHICLE_TYPES.TRAILER) { - return [...trailerSubtypes]; - } - return [EMPTY_VEHICLE_SUBTYPE]; -}; - -// Returns eligible subset of subtype options to be used by select field for vehicle subtype -const getEligibleSubtypeOptions = ( - powerUnitSubtypes: VehicleSubType[], - trailerSubtypes: VehicleSubType[], - ineligiblePowerUnitSubtypes: VehicleSubType[], - ineligibleTrailerSubtypes: VehicleSubType[], - allowedLOAPowerUnitSubtypes: string[], - allowedLOATrailerSubtypes: string[], - vehicleType?: string, -) => { - if ( - vehicleType !== VEHICLE_TYPES.POWER_UNIT && - vehicleType !== VEHICLE_TYPES.TRAILER - ) { - return [EMPTY_VEHICLE_SUBTYPE]; - } - - // Sort vehicle subtypes alphabetically - const sortedVehicleSubtypes = sortVehicleSubTypes( - vehicleType, - getSubtypeOptions(vehicleType, powerUnitSubtypes, trailerSubtypes), - ); - - return filterVehicleSubtypes( - sortedVehicleSubtypes, - vehicleType, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, - allowedLOAPowerUnitSubtypes, - allowedLOATrailerSubtypes, - ); -}; - export const VehicleDetails = ({ feature, vehicleFormData, vehicleOptions, - powerUnitSubtypes, - trailerSubtypes, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, - selectedLOAs, + subtypeOptions, + isSelectedLOAVehicle, onSetSaveVehicle, onSetVehicle, onClearVehicle, @@ -126,11 +60,8 @@ export const VehicleDetails = ({ feature: string; vehicleFormData: PermitVehicleDetails; vehicleOptions: Vehicle[]; - powerUnitSubtypes: VehicleSubType[]; - trailerSubtypes: VehicleSubType[]; - ineligiblePowerUnitSubtypes: VehicleSubType[]; - ineligibleTrailerSubtypes: VehicleSubType[]; - selectedLOAs: LOADetail[]; + subtypeOptions: VehicleSubType[]; + isSelectedLOAVehicle: boolean; onSetSaveVehicle: (saveVehicle: boolean) => void; onSetVehicle: (vehicleDetails: PermitVehicleDetails) => void; onClearVehicle: (saveVehicle: boolean) => void; @@ -168,77 +99,6 @@ export const VehicleDetails = ({ const disableVehicleTypeSelect = shouldDisableVehicleTypeSelect(); - // Options for the vehicle subtype field (based on vehicle type) - const [subtypeOptions, setSubtypeOptions] = useState([ - EMPTY_VEHICLE_SUBTYPE, - ]); - - // Find vehicle subtypes that are allowed by LOAs - const permittedLOAPowerUnitIds = new Set([ - ...selectedLOAs.map(loa => loa.powerUnits) - .reduce((prevPowerUnits, currPowerUnits) => [ - ...prevPowerUnits, - ...currPowerUnits, - ], []), - ]); - - const permittedLOATrailerIds = new Set([ - ...selectedLOAs.map(loa => loa.trailers) - .reduce((prevTrailers, currTrailers) => [ - ...prevTrailers, - ...currTrailers, - ], []), - ]); - - const powerUnitsInInventory = vehicleOptions - .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.POWER_UNIT) as PowerUnit[]; - - const trailersInInventory = vehicleOptions - .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.TRAILER) as Trailer[]; - - const permittedLOAPowerUnitSubtypes = powerUnitsInInventory - .filter(powerUnit => permittedLOAPowerUnitIds.has(powerUnit.powerUnitId as string)) - .map(powerUnit => powerUnit.powerUnitTypeCode); - - const permittedLOATrailerSubtypes = trailersInInventory - .filter(trailer => permittedLOATrailerIds.has(trailer.trailerId as string)) - .map(trailer => trailer.trailerTypeCode); - - // Check if selected vehicle is an LOA vehicle - const isSelectedVehicleAllowedByLOA = Boolean(vehicleFormData.vehicleId) - && ( - permittedLOAPowerUnitIds.has(vehicleFormData.vehicleId as string) - || permittedLOATrailerIds.has(vehicleFormData.vehicleId as string) - ) - && ( - powerUnitsInInventory.map(powerUnit => powerUnit.powerUnitId) - .includes(vehicleFormData.vehicleId as string) - || trailersInInventory.map(trailer => trailer.trailerId) - .includes(vehicleFormData.vehicleId as string) - ); - - useEffect(() => { - // Update subtype options when vehicle type changes - const subtypes = getEligibleSubtypeOptions( - powerUnitSubtypes, - trailerSubtypes, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, - permittedLOAPowerUnitSubtypes, - permittedLOATrailerSubtypes, - vehicleType, - ); - setSubtypeOptions(subtypes); - }, [ - powerUnitSubtypes, - trailerSubtypes, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, - vehicleType, - permittedLOAPowerUnitSubtypes, - permittedLOATrailerSubtypes, - ]); - // Set the "Save to Inventory" radio button to false on render useEffect(() => { onSetSaveVehicle(saveVehicle); @@ -314,10 +174,10 @@ export const VehicleDetails = ({ // If the selected vehicle is an LOA vehicle, it should not be edited/saved to inventory useEffect(() => { - if (isSelectedVehicleAllowedByLOA) { + if (isSelectedLOAVehicle) { setSaveVehicle(false); } - }, [isSelectedVehicleAllowedByLOA]); + }, [isSelectedLOAVehicle]); return ( @@ -367,9 +227,6 @@ export const VehicleDetails = ({ vehicleOptions={vehicleOptions} handleClearVehicle={() => onClearVehicle(saveVehicle)} handleSelectVehicle={onSelectVehicle} - ineligiblePowerUnitSubtypes={ineligiblePowerUnitSubtypes.map(({ typeCode }) => typeCode)} - ineligibleTrailerSubtypes={ineligibleTrailerSubtypes.map(({ typeCode }) => typeCode)} - loas={selectedLOAs} /> @@ -387,8 +244,8 @@ export const VehicleDetails = ({ width: formFieldStyle.width, customHelperText: "last 6 digits", }} - readOnly={isSelectedVehicleAllowedByLOA} - disabled={isSelectedVehicleAllowedByLOA} + readOnly={isSelectedLOAVehicle} + disabled={isSelectedLOAVehicle} /> ))} - readOnly={isSelectedVehicleAllowedByLOA} - disabled={isSelectedVehicleAllowedByLOA} + readOnly={isSelectedLOAVehicle} + disabled={isSelectedLOAVehicle} /> @@ -534,8 +391,8 @@ export const VehicleDetails = ({ "data-testid": "save-vehicle-yes", } as CustomInputHTMLAttributes } - readOnly={isSelectedVehicleAllowedByLOA} - disabled={isSelectedVehicleAllowedByLOA} + readOnly={isSelectedLOAVehicle} + disabled={isSelectedLOAVehicle} /> } label="Yes" @@ -550,8 +407,8 @@ export const VehicleDetails = ({ "data-testid": "save-vehicle-no", } as CustomInputHTMLAttributes } - readOnly={isSelectedVehicleAllowedByLOA} - disabled={isSelectedVehicleAllowedByLOA} + readOnly={isSelectedLOAVehicle} + disabled={isSelectedLOAVehicle} /> } label="No" diff --git a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx index 4505eefc4..feeab5e40 100644 --- a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Autocomplete, FormControl, @@ -12,12 +12,10 @@ import { import "./SelectVehicleDropdown.scss"; import { getDefaultRequiredVal } from "../../../../../../../../common/helpers/util"; import { sortVehicles } from "../../../../../../helpers/sorter"; -import { filterVehicles } from "../../../../../../helpers/permitVehicles"; import { VEHICLE_CHOOSE_FROM } from "../../../../../../constants/constants"; import { EMPTY_VEHICLE_UNIT_NUMBER } from "../../../../../../../../common/constants/constants"; import { Nullable } from "../../../../../../../../common/types/common"; import { PermitVehicleDetails } from "../../../../../../types/PermitVehicleDetails"; -import { LOADetail } from "../../../../../../../settings/types/SpecialAuthorization"; import { PowerUnit, Trailer, @@ -50,9 +48,6 @@ export const SelectVehicleDropdown = ({ vehicleOptions, handleSelectVehicle, handleClearVehicle, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, - loas, }: { chooseFrom: string; selectedVehicle: Nullable; @@ -60,18 +55,11 @@ export const SelectVehicleDropdown = ({ vehicleOptions: Vehicle[]; handleSelectVehicle: (vehicle: Vehicle) => void; handleClearVehicle: () => void; - ineligiblePowerUnitSubtypes: string[]; - ineligibleTrailerSubtypes: string[]; - loas: LOADetail[]; }) => { - const sortedVehicles = sortVehicles(chooseFrom, vehicleOptions); - - const eligibleVehicles = filterVehicles( - sortedVehicles, - ineligiblePowerUnitSubtypes, - ineligibleTrailerSubtypes, - loas, - ); + const eligibleVehicles = useMemo(() => sortVehicles( + chooseFrom, + vehicleOptions, + ), [chooseFrom, vehicleOptions]); const selectedOption = selectedVehicle ? getDefaultRequiredVal( diff --git a/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx b/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx index 6abb845f1..851441d4e 100644 --- a/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx @@ -79,20 +79,31 @@ export const renderTestComponent = ( const user = userEvent.setup(userEventOptions); let selectedConditions = [...conditions]; const expiryDate = getExpiryDate(startDate, duration); + const allConditions = getDefaultConditions(permitType, false) + .map(condition => { + const existingCondition = selectedConditions + .find(c => c.condition === condition.condition); + + return { + ...condition, + checked: existingCondition + ? existingCondition.checked + : condition.checked, + }; + }); + const renderedComponent = render( ({ label: duration.text, value: duration.days, }))} disableStartDate={false} - permitType={permitType} pastStartDateStatus={PAST_START_DATE_STATUSES.FAIL} - includeLcvCondition={false} onSetConditions={(updatedConditions) => { selectedConditions = [...updatedConditions]; }} diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx index 498be851a..e91c28ca2 100644 --- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx @@ -18,7 +18,7 @@ import { PermitVehicleDetails } from "../../../../types/PermitVehicleDetails"; import { Application } from "../../../../types/application"; import { PermitCondition } from "../../../../types/PermitCondition"; import { ReviewPermitLOAs } from "./ReviewPermitLOAs"; -import { LOADetail } from "../../../../../settings/types/SpecialAuthorization"; +import { PermitLOA } from "../../../../types/PermitLOA"; import { PERMIT_REVIEW_CONTEXTS, PermitReviewContext, @@ -58,7 +58,7 @@ interface PermitReviewProps { oldFields?: Nullable>; calculatedFee: string; doingBusinessAs?: Nullable; - loas?: Nullable; + loas?: Nullable; } export const PermitReview = (props: PermitReviewProps) => { diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewContactDetails.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewContactDetails.tsx index 1ba8f1fd3..473a5d03e 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewContactDetails.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewContactDetails.tsx @@ -4,10 +4,8 @@ import "./ReviewContactDetails.scss"; import { DiffChip } from "./DiffChip"; import { Nullable } from "../../../../../../common/types/common"; import { PermitContactDetails } from "../../../../types/PermitContactDetails"; -import { - areValuesDifferent, - getDefaultRequiredVal, -} from "../../../../../../common/helpers/util"; +import { getDefaultRequiredVal } from "../../../../../../common/helpers/util"; +import { areValuesDifferent } from "../../../../../../common/helpers/equality"; const nameDisplay = (firstName?: Nullable, lastName?: Nullable) => { if (!firstName) return getDefaultRequiredVal("", lastName); diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx index 486fe17c1..ff44a2fdb 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx @@ -8,11 +8,8 @@ import { DiffChip } from "./DiffChip"; import { Nullable } from "../../../../../../common/types/common"; import { PermitCondition } from "../../../../types/PermitCondition"; import { BASE_DAYS_IN_YEAR } from "../../../../constants/constants"; -import { - applyWhenNotNullable, - areValuesDifferent, -} from "../../../../../../common/helpers/util"; - +import { applyWhenNotNullable } from "../../../../../../common/helpers/util"; +import { areValuesDifferent } from "../../../../../../common/helpers/equality"; import { DATE_FORMATS, dayjsToLocalStr, diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitLOAs.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitLOAs.tsx index 3bf34c5a0..e0c98aa1b 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitLOAs.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitLOAs.tsx @@ -3,12 +3,12 @@ import { Box, Typography } from "@mui/material"; import "./ReviewPermitLOAs.scss"; import { LOATable } from "../form/LOATable"; import { Nullable } from "../../../../../../common/types/common"; -import { LOADetail } from "../../../../../settings/types/SpecialAuthorization"; +import { PermitLOA } from "../../../../types/PermitLOA"; export const ReviewPermitLOAs = ({ loas, }: { - loas?: Nullable; + loas?: Nullable; }) => { return loas && loas.length > 0 ? ( diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewVehicleInfo.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewVehicleInfo.tsx index b7cae4459..284727a32 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewVehicleInfo.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewVehicleInfo.tsx @@ -4,7 +4,7 @@ import { faCircleCheck } from "@fortawesome/free-regular-svg-icons"; import "./ReviewVehicleInfo.scss"; import { DiffChip } from "./DiffChip"; -import { areValuesDifferent } from "../../../../../../common/helpers/util"; +import { areValuesDifferent } from "../../../../../../common/helpers/equality"; import { Nullable } from "../../../../../../common/types/common"; import { PermitVehicleDetails } from "../../../../types/PermitVehicleDetails"; import { diff --git a/frontend/src/features/permits/types/PermitCondition.ts b/frontend/src/features/permits/types/PermitCondition.ts index 9c57bba4a..24e43ae7a 100644 --- a/frontend/src/features/permits/types/PermitCondition.ts +++ b/frontend/src/features/permits/types/PermitCondition.ts @@ -5,3 +5,11 @@ export interface PermitCondition { checked: boolean; disabled?: boolean; } + +export const arePermitConditionEqual = ( + condition1: PermitCondition, + condition2: PermitCondition, +) => { + return condition1.condition === condition2.condition + && condition1.checked === condition2.checked; +}; diff --git a/frontend/src/features/permits/types/PermitData.ts b/frontend/src/features/permits/types/PermitData.ts index c33774c62..c937ef514 100644 --- a/frontend/src/features/permits/types/PermitData.ts +++ b/frontend/src/features/permits/types/PermitData.ts @@ -4,8 +4,8 @@ import { Nullable } from "../../../common/types/common"; import { PermitContactDetails } from "./PermitContactDetails"; import { PermitVehicleDetails } from "./PermitVehicleDetails"; import { PermitMailingAddress } from "./PermitMailingAddress"; -import { LOADetail } from "../../settings/types/SpecialAuthorization"; import { PermitCondition } from "./PermitCondition"; +import { PermitLOA } from "./PermitLOA"; export interface PermitData { startDate: Dayjs; @@ -19,5 +19,5 @@ export interface PermitData { companyName?: Nullable; doingBusinessAs?: Nullable; clientNumber?: Nullable; - loas?: Nullable; + loas?: Nullable; } diff --git a/frontend/src/features/permits/types/PermitLOA.ts b/frontend/src/features/permits/types/PermitLOA.ts new file mode 100644 index 000000000..6d78b5af3 --- /dev/null +++ b/frontend/src/features/permits/types/PermitLOA.ts @@ -0,0 +1,41 @@ +import { areValuesDifferent, doUniqueArraysHaveSameItems } from "../../../common/helpers/equality"; +import { Nullable } from "../../../common/types/common"; +import { PermitType } from "./PermitType"; + +export interface PermitLOA { + loaId: number; + loaNumber: number; + companyId: number; + startDate: string; + expiryDate?: Nullable; + loaPermitType: PermitType[]; + powerUnits: string[]; + trailers: string[]; + originalLoaId: number; + previousLoaId?: Nullable; +} + +/** + * Determine whether or not two permit LOAs have the same details. + * @param loa1 First permit LOA + * @param loa2 Second permit LOA + * @returns Whether or not the two permit LOAs have the same details + */ +export const arePermitLOADetailsEqual = ( + loa1?: Nullable, + loa2?: Nullable, +) => { + if (!loa1 && !loa2) return true; + if (!loa1 || !loa2) return false; + + return loa1.loaId === loa2.loaId + && loa1.loaNumber === loa2.loaNumber + && loa1.companyId === loa2.companyId + && loa1.startDate === loa2.startDate + && !areValuesDifferent(loa1.expiryDate, loa2.expiryDate) + && doUniqueArraysHaveSameItems(loa1.loaPermitType, loa2.loaPermitType) + && doUniqueArraysHaveSameItems(loa1.powerUnits, loa2.powerUnits) + && doUniqueArraysHaveSameItems(loa1.trailers, loa2.trailers) + && loa1.originalLoaId === loa2.originalLoaId + && !areValuesDifferent(loa1.previousLoaId, loa2.previousLoaId); +}; diff --git a/frontend/src/features/settings/apiManager/loa.ts b/frontend/src/features/settings/apiManager/loa.ts new file mode 100644 index 000000000..161f204f7 --- /dev/null +++ b/frontend/src/features/settings/apiManager/loa.ts @@ -0,0 +1,131 @@ +import { AxiosResponse } from "axios"; + +import { LOADetail } from "../types/LOADetail"; +import { LOAFormData, serializeLOAFormData } from "../types/LOAFormData"; +import { SPECIAL_AUTH_API_ROUTES } from "./endpoints/endpoints"; +import { streamDownloadFile } from "../../../common/helpers/util"; +import { + httpDELETERequest, + httpGETRequest, + httpGETRequestStream, + httpPOSTRequestWithFile, + httpPUTRequestWithFile, +} from "../../../common/apiManager/httpRequestHandler"; + +/** + * Get the LOAs for a given company. + * @param companyId Company id of the company to get LOAs for + * @param expired Whether or not to only fetch expired LOAs + * @returns LOAs for the given company + */ +export const getLOAs = async ( + companyId: number | string, + expired: boolean, +): Promise => { + const response = await httpGETRequest( + SPECIAL_AUTH_API_ROUTES.LOA.ALL(companyId, expired), + ); + return response.data; +}; + +/** + * Get the LOA detail for a specific LOA. + * @param companyId Company id of the company to get LOA for + * @param loaId id of the LOA to fetch + * @returns LOA detail for a given LOA + */ +export const getLOADetail = async ( + companyId: number | string, + loaId: number, +): Promise => { + const response = await httpGETRequest( + SPECIAL_AUTH_API_ROUTES.LOA.DETAIL(companyId, loaId), + ); + return response.data; +}; + +/** + * Create an LOA for a company. + * @param LOAData Information about the LOA to be created for the company + * @returns Result of creating the LOA, or error on fail + */ +export const createLOA = async ( + LOAData: { + companyId: number | string; + data: LOAFormData; + }, +): Promise> => { + const { companyId, data } = LOAData; + return await httpPOSTRequestWithFile( + SPECIAL_AUTH_API_ROUTES.LOA.CREATE(companyId), + serializeLOAFormData(data), + ); +}; + +/** + * Update an LOA for a company. + * @param LOAData Information about the LOA to be updated for the company + * @returns Result of updating the LOA, or error on fail + */ +export const updateLOA = async ( + LOAData: { + companyId: number | string; + loaId: number; + data: LOAFormData; + }, +): Promise> => { + const { companyId, loaId, data } = LOAData; + return await httpPUTRequestWithFile( + SPECIAL_AUTH_API_ROUTES.LOA.UPDATE(companyId, loaId), + serializeLOAFormData(data), + ); +}; + +/** + * Remove an LOA for a company. + * @param LOAData LOA id and id of the company to remove it from + * @returns Result of removing the LOA, or error on fail + */ +export const removeLOA = async ( + LOAData: { + companyId: number | string; + loaId: number; + }, +): Promise> => { + const { companyId, loaId } = LOAData; + return await httpDELETERequest( + SPECIAL_AUTH_API_ROUTES.LOA.REMOVE(companyId, loaId), + ); +}; + +/** + * Download LOA. + * @param loaId id of the LOA to download + * @param companyId id of the company that the LOA belongs to + * @returns A Promise containing the dms reference string for the LOA download stream + */ +export const downloadLOA = async ( + loaId: number, + companyId: string | number, +) => { + const url = SPECIAL_AUTH_API_ROUTES.LOA.DOWNLOAD(companyId, loaId); + const response = await httpGETRequestStream(url); + return await streamDownloadFile(response); +}; + +/** + * Remove an LOA document. + * @param LOAData LOA id and id of the company to remove it from + * @returns Result of removing the LOA document, or error on fail + */ +export const removeLOADocument = async ( + LOAData: { + companyId: number | string; + loaId: number; + }, +): Promise> => { + const { companyId, loaId } = LOAData; + return await httpDELETERequest( + SPECIAL_AUTH_API_ROUTES.LOA.REMOVE_DOCUMENT(companyId, loaId), + ); +}; diff --git a/frontend/src/features/settings/apiManager/specialAuthorization.ts b/frontend/src/features/settings/apiManager/specialAuthorization.ts index 7448be6ca..71472e957 100644 --- a/frontend/src/features/settings/apiManager/specialAuthorization.ts +++ b/frontend/src/features/settings/apiManager/specialAuthorization.ts @@ -1,137 +1,13 @@ import { AxiosResponse } from "axios"; -import { LOADetail, NoFeePermitType, SpecialAuthorizationData } from "../types/SpecialAuthorization"; -import { LOAFormData, serializeLOAFormData } from "../types/LOAFormData"; +import { NoFeePermitType, SpecialAuthorizationData } from "../types/SpecialAuthorization"; import { SPECIAL_AUTH_API_ROUTES } from "./endpoints/endpoints"; -import { streamDownloadFile } from "../../../common/helpers/util"; import { RequiredOrNull } from "../../../common/types/common"; import { - httpDELETERequest, httpGETRequest, - httpGETRequestStream, - httpPOSTRequestWithFile, httpPUTRequest, - httpPUTRequestWithFile, } from "../../../common/apiManager/httpRequestHandler"; -/** - * Get the LOAs for a given company. - * @param companyId Company id of the company to get LOAs for - * @param expired Whether or not to only fetch expired LOAs - * @returns LOAs for the given company - */ -export const getLOAs = async ( - companyId: number | string, - expired: boolean, -): Promise => { - const response = await httpGETRequest( - SPECIAL_AUTH_API_ROUTES.LOA.ALL(companyId, expired), - ); - return response.data; -}; - -/** - * Get the LOA detail for a specific LOA. - * @param companyId Company id of the company to get LOA for - * @param loaId id of the LOA to fetch - * @returns LOA detail for a given LOA - */ -export const getLOADetail = async ( - companyId: number | string, - loaId: number, -): Promise => { - const response = await httpGETRequest( - SPECIAL_AUTH_API_ROUTES.LOA.DETAIL(companyId, loaId), - ); - return response.data; -}; - -/** - * Create an LOA for a company. - * @param LOAData Information about the LOA to be created for the company - * @returns Result of creating the LOA, or error on fail - */ -export const createLOA = async ( - LOAData: { - companyId: number | string; - data: LOAFormData; - }, -): Promise> => { - const { companyId, data } = LOAData; - return await httpPOSTRequestWithFile( - SPECIAL_AUTH_API_ROUTES.LOA.CREATE(companyId), - serializeLOAFormData(data), - ); -}; - -/** - * Update an LOA for a company. - * @param LOAData Information about the LOA to be updated for the company - * @returns Result of updating the LOA, or error on fail - */ -export const updateLOA = async ( - LOAData: { - companyId: number | string; - loaId: number; - data: LOAFormData; - }, -): Promise> => { - const { companyId, loaId, data } = LOAData; - return await httpPUTRequestWithFile( - SPECIAL_AUTH_API_ROUTES.LOA.UPDATE(companyId, loaId), - serializeLOAFormData(data), - ); -}; - -/** - * Remove an LOA for a company. - * @param LOAData LOA id and id of the company to remove it from - * @returns Result of removing the LOA, or error on fail - */ -export const removeLOA = async ( - LOAData: { - companyId: number | string; - loaId: number; - }, -): Promise> => { - const { companyId, loaId } = LOAData; - return await httpDELETERequest( - SPECIAL_AUTH_API_ROUTES.LOA.REMOVE(companyId, loaId), - ); -}; - -/** - * Download LOA. - * @param loaId id of the LOA to download - * @param companyId id of the company that the LOA belongs to - * @returns A Promise containing the dms reference string for the LOA download stream - */ -export const downloadLOA = async ( - loaId: number, - companyId: string | number, -) => { - const url = SPECIAL_AUTH_API_ROUTES.LOA.DOWNLOAD(companyId, loaId); - const response = await httpGETRequestStream(url); - return await streamDownloadFile(response); -}; - -/** - * Remove an LOA document. - * @param LOAData LOA id and id of the company to remove it from - * @returns Result of removing the LOA document, or error on fail - */ -export const removeLOADocument = async ( - LOAData: { - companyId: number | string; - loaId: number; - }, -): Promise> => { - const { companyId, loaId } = LOAData; - return await httpDELETERequest( - SPECIAL_AUTH_API_ROUTES.LOA.REMOVE_DOCUMENT(companyId, loaId), - ); -}; - /** * Get the special authorizations info for a given company. * @param companyId Company id of the company to get special authorizations info for diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx index 06ec26564..c54381b3f 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx @@ -4,7 +4,7 @@ import { faClockRotateLeft } from "@fortawesome/free-solid-svg-icons"; import "./ExpiredLOAModal.scss"; import { LOAList } from "../list/LOAList"; -import { LOADetail } from "../../../../types/SpecialAuthorization"; +import { LOADetail } from "../../../../types/LOADetail"; export const ExpiredLOAModal = ({ showModal, diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx index 666f7a19c..848894506 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx @@ -1,7 +1,7 @@ import { MRT_Row } from "material-react-table"; import { CustomActionLink } from "../../../../../../common/components/links/CustomActionLink"; -import { LOADetail } from "../../../../types/SpecialAuthorization"; +import { LOADetail } from "../../../../types/LOADetail"; export const LOADownloadCell = ({ onDownload, diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx index 69d3910e8..97c14e0aa 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx @@ -9,7 +9,7 @@ import { } from "material-react-table"; import "./LOAList.scss"; -import { LOADetail } from "../../../../types/SpecialAuthorization"; +import { LOADetail } from "../../../../types/LOADetail"; import { LOAListColumnDef } from "./LOAListColumnDef"; import { defaultTableInitialStateOptions, diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx index bec21ad11..ea09f28e4 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx @@ -1,6 +1,6 @@ import { MRT_ColumnDef, MRT_Row } from "material-react-table"; -import { LOADetail } from "../../../../types/SpecialAuthorization"; +import { LOADetail } from "../../../../types/LOADetail"; import { DATE_FORMATS, toLocal } from "../../../../../../common/helpers/formatDate"; import { applyWhenNotNullable } from "../../../../../../common/helpers/util"; import { LOANumberCell } from "./LOANumberCell"; diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx index 4301b5c81..67a536b67 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx @@ -1,6 +1,6 @@ import { MRT_Row } from "material-react-table"; -import { LOADetail } from "../../../../types/SpecialAuthorization"; +import { LOADetail } from "../../../../types/LOADetail"; import { CustomActionLink } from "../../../../../../common/components/links/CustomActionLink"; export const LOANumberCell = ({ diff --git a/frontend/src/features/settings/helpers/permissions.ts b/frontend/src/features/settings/helpers/permissions.ts index 7efdf5822..2a63ee5d6 100644 --- a/frontend/src/features/settings/helpers/permissions.ts +++ b/frontend/src/features/settings/helpers/permissions.ts @@ -82,6 +82,7 @@ export const canUpdateLCVFlag = ( ): boolean => { return ( userRole === USER_ROLE.HQ_ADMINISTRATOR || + userRole === USER_ROLE.SYSTEM_ADMINISTRATOR || Boolean(DoesUserHaveClaim(userClaims, CLAIMS.WRITE_LCV_FLAG)) ); }; diff --git a/frontend/src/features/settings/hooks/LOA.ts b/frontend/src/features/settings/hooks/LOA.ts index bb3d8cb6e..087a8bc6a 100644 --- a/frontend/src/features/settings/hooks/LOA.ts +++ b/frontend/src/features/settings/hooks/LOA.ts @@ -1,4 +1,8 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; import { Nullable } from "../../../common/types/common"; import { @@ -8,7 +12,7 @@ import { removeLOA, removeLOADocument, updateLOA, -} from "../apiManager/specialAuthorization"; +} from "../apiManager/loa"; const QUERY_KEYS = { LOAS: (expired: boolean) => ["loas", expired], diff --git a/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx b/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx index 410938068..042ba3f2c 100644 --- a/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx +++ b/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx @@ -16,7 +16,7 @@ import { DEFAULT_NO_FEE_PERMIT_TYPE, NoFeePermitType } from "../../types/Special import { NoFeePermitsSection } from "../../components/SpecialAuthorizations/NoFeePermits/NoFeePermitsSection"; import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; import { LCVSection } from "../../components/SpecialAuthorizations/LCV/LCVSection"; -import { downloadLOA } from "../../apiManager/specialAuthorization"; +import { downloadLOA } from "../../apiManager/loa"; import { useFetchSpecialAuthorizations, useUpdateLCV, diff --git a/frontend/src/features/settings/types/LOADetail.ts b/frontend/src/features/settings/types/LOADetail.ts new file mode 100644 index 000000000..e990e678a --- /dev/null +++ b/frontend/src/features/settings/types/LOADetail.ts @@ -0,0 +1,38 @@ +import { Nullable } from "../../../common/types/common"; +import { PermitType } from "../../permits/types/PermitType"; + +export interface LOADetail { + loaId: number; + loaNumber: number; + companyId: number; + startDate: string; + expiryDate?: Nullable; + documentId: string; + fileName: string; + loaPermitType: PermitType[]; + comment?: Nullable; + powerUnits: string[]; + trailers: string[]; + originalLoaId: number; + previousLoaId?: Nullable; +} + +export interface CreateLOARequestData { + startDate: string; + expiryDate?: Nullable; + loaPermitType: PermitType[]; + // document: Buffer; + comment?: Nullable; + powerUnits: string[]; + trailers: string[]; +} + +export interface UpdateLOARequestData { + startDate: string; + expiryDate?: Nullable; + loaPermitType: PermitType[]; + // document?: Buffer; + comment?: Nullable; + powerUnits: string[]; + trailers: string[]; +} diff --git a/frontend/src/features/settings/types/LOAFormData.ts b/frontend/src/features/settings/types/LOAFormData.ts index 5d5d343a1..68011d7b9 100644 --- a/frontend/src/features/settings/types/LOAFormData.ts +++ b/frontend/src/features/settings/types/LOAFormData.ts @@ -3,7 +3,7 @@ import { Dayjs } from "dayjs"; import { Nullable } from "../../../common/types/common"; import { PERMIT_TYPES } from "../../permits/types/PermitType"; import { LOAVehicle } from "./LOAVehicle"; -import { LOADetail } from "./SpecialAuthorization"; +import { LOADetail } from "./LOADetail"; import { applyWhenNotNullable, getDefaultRequiredVal, diff --git a/frontend/src/features/settings/types/SpecialAuthorization.ts b/frontend/src/features/settings/types/SpecialAuthorization.ts index 4392c906c..792e38806 100644 --- a/frontend/src/features/settings/types/SpecialAuthorization.ts +++ b/frontend/src/features/settings/types/SpecialAuthorization.ts @@ -1,6 +1,4 @@ -import { areArraysEqual } from "../../../common/helpers/util"; -import { Nullable, RequiredOrNull } from "../../../common/types/common"; -import { PermitType } from "../../permits/types/PermitType"; +import { RequiredOrNull } from "../../../common/types/common"; export const NO_FEE_PERMIT_TYPES = { CA_GOVT: "CA_GOVT", @@ -29,69 +27,6 @@ export const noFeePermitTypeDescription = (noFeePermitType: NoFeePermitType) => } }; -export interface LOADetail { - loaId: number; - loaNumber: number; - companyId: number; - startDate: string; - expiryDate?: Nullable; - documentId: string; - fileName: string; - loaPermitType: PermitType[]; - comment?: Nullable; - powerUnits: string[]; - trailers: string[]; - originalLoaId: number; - previousLoaId?: Nullable; -} - -export interface CreateLOARequestData { - startDate: string; - expiryDate?: Nullable; - loaPermitType: PermitType[]; - // document: Buffer; - comment?: Nullable; - powerUnits: string[]; - trailers: string[]; -} - -export interface UpdateLOARequestData { - startDate: string; - expiryDate?: Nullable; - loaPermitType: PermitType[]; - // document?: Buffer; - comment?: Nullable; - powerUnits: string[]; - trailers: string[]; -} - -/** - * Determine whether or not two LOAs have the same details. - * @param loa1 First LOA - * @param loa2 Second LOA - * @returns Whether or not the two LOAs have the same details - */ -export const areLOADetailsEqual = ( - loa1?: Nullable, - loa2?: Nullable, -) => { - if (!loa1 && !loa2) return true; - if (!loa1 || !loa2) return false; - - return loa1.loaId === loa2.loaId - && loa1.loaNumber === loa2.loaNumber - && loa1.companyId === loa2.companyId - && loa1.startDate === loa2.startDate - && loa1.expiryDate === loa2.expiryDate - && loa1.documentId === loa2.documentId - && loa1.fileName === loa2.fileName - && areArraysEqual(loa1.loaPermitType, loa2.loaPermitType) - && loa1.comment === loa2.comment - && areArraysEqual(loa1.powerUnits, loa2.powerUnits) - && areArraysEqual(loa1.trailers, loa2.trailers) - && loa1.originalLoaId === loa2.originalLoaId - && loa1.previousLoaId === loa2.previousLoaId; -}; export interface SpecialAuthorizationData { companyId: number; specialAuthId: number; From 63b9ed9d08635b60695b2f3c73b9b5e23505d021 Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Mon, 21 Oct 2024 10:51:40 -0700 Subject: [PATCH 44/58] ORV2-2834 - FE: Update: Change Start Application Menu and Steps (#1635) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../components/feeSummary/FeeSummary.tsx | 19 +- .../components/form/ApplicationDetails.tsx | 4 +- .../form/tests/ApplicationDetails.test.tsx | 4 +- .../features/permits/constants/constants.ts | 52 +++- .../dashboard/SelectPermitType.scss | 27 --- .../components/dashboard/SelectPermitType.tsx | 36 --- .../dashboard/StartApplicationAction.scss | 150 +++++++++++- .../dashboard/StartApplicationAction.tsx | 187 ++++++++++++--- .../components/pay/ApplicationSummary.tsx | 7 +- .../tests/ApplicationReview.test.tsx | 13 +- .../permits/pages/Refund/RefundPage.tsx | 4 +- .../Void/components/VoidPermitHeader.tsx | 10 +- .../features/permits/types/PermitCategory.ts | 51 ++++ .../src/features/permits/types/PermitType.ts | 224 ++++++++++++------ frontend/src/themes/orbcStyles.scss | 1 + 15 files changed, 572 insertions(+), 217 deletions(-) delete mode 100644 frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.scss delete mode 100644 frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.tsx create mode 100644 frontend/src/features/permits/types/PermitCategory.ts diff --git a/frontend/src/features/permits/components/feeSummary/FeeSummary.tsx b/frontend/src/features/permits/components/feeSummary/FeeSummary.tsx index 27ecfd7f8..7f51402a3 100644 --- a/frontend/src/features/permits/components/feeSummary/FeeSummary.tsx +++ b/frontend/src/features/permits/components/feeSummary/FeeSummary.tsx @@ -1,6 +1,6 @@ import { Nullable } from "../../../../common/types/common"; import { feeSummaryDisplayText } from "../../helpers/feeSummary"; -import { PermitType, permitTypeDisplayText } from "../../types/PermitType"; +import { getPermitTypeName, PermitType } from "../../types/PermitType"; import "./FeeSummary.scss"; export const FeeSummary = ({ @@ -14,7 +14,11 @@ export const FeeSummary = ({ permitDuration?: number; hideDescriptions?: boolean; }) => { - const feeDisplayText = feeSummaryDisplayText(feeSummary, permitDuration, permitType); + const feeDisplayText = feeSummaryDisplayText( + feeSummary, + permitDuration, + permitType, + ); return (
@@ -28,17 +32,20 @@ export const FeeSummary = ({
-
- {permitTypeDisplayText(permitType)} +
+ {getPermitTypeName(permitType)}
- +
{feeDisplayText}
)} - +
Total (CAD)
diff --git a/frontend/src/features/permits/components/form/ApplicationDetails.tsx b/frontend/src/features/permits/components/form/ApplicationDetails.tsx index bb9842061..9ed8ae86b 100644 --- a/frontend/src/features/permits/components/form/ApplicationDetails.tsx +++ b/frontend/src/features/permits/components/form/ApplicationDetails.tsx @@ -4,7 +4,7 @@ import { Dayjs } from "dayjs"; import { CompanyBanner } from "../../../../common/components/banners/CompanyBanner"; import { CompanyInformation } from "./CompanyInformation"; import "./ApplicationDetails.scss"; -import { permitTypeDisplayText } from "../../types/PermitType"; +import { getPermitTypeName } from "../../types/PermitType"; import { CompanyProfile } from "../../../manageProfile/types/manageProfile"; import { Nullable } from "../../../../common/types/common"; import { @@ -35,7 +35,7 @@ export const ApplicationDetails = ({ isAmendAction?: Nullable; doingBusinessAs?: Nullable; }) => { - const applicationName = permitTypeDisplayText( + const applicationName = getPermitTypeName( getDefaultRequiredVal("", permitType), ); diff --git a/frontend/src/features/permits/components/form/tests/ApplicationDetails.test.tsx b/frontend/src/features/permits/components/form/tests/ApplicationDetails.test.tsx index a8d68ef06..3ac3911ee 100644 --- a/frontend/src/features/permits/components/form/tests/ApplicationDetails.test.tsx +++ b/frontend/src/features/permits/components/form/tests/ApplicationDetails.test.tsx @@ -1,4 +1,4 @@ -import { permitTypeDisplayText } from "../../../types/PermitType"; +import { getPermitTypeName } from "../../../types/PermitType"; import { DATE_FORMATS, dayjsToLocalStr, @@ -57,7 +57,7 @@ describe("Application Details Display", () => { ); // Assert - expect(await title()).toHaveTextContent(permitTypeDisplayText(permitType)); + expect(await title()).toHaveTextContent(getPermitTypeName(permitType)); expect(await applicationNumber()).toHaveTextContent( defaultApplicationNumber, ); diff --git a/frontend/src/features/permits/constants/constants.ts b/frontend/src/features/permits/constants/constants.ts index 8f7639fef..ef343f2a0 100644 --- a/frontend/src/features/permits/constants/constants.ts +++ b/frontend/src/features/permits/constants/constants.ts @@ -1,5 +1,14 @@ import { VEHICLE_TYPES } from "../../manageVehicles/types/Vehicle"; -import { EMPTY_PERMIT_TYPE_SELECT, PERMIT_TYPES, getPermitTypeName } from "../types/PermitType"; +import { + getPermitCategoryName, + PERMIT_CATEGORIES, + PermitCategory, +} from "../types/PermitCategory"; +import { + PermitType, + TERM_PERMIT_LIST, + getPermitTypeShortName, +} from "../types/PermitType"; export const VEHICLE_CHOOSE_FROM = { UNIT_NUMBER: "unitNumber", @@ -19,12 +28,45 @@ export const VEHICLE_TYPE_OPTIONS = [ { value: VEHICLE_TYPES.TRAILER, label: "Trailer" }, ]; -export const PERMIT_TYPE_CHOOSE_FROM_OPTIONS = [ - { value: EMPTY_PERMIT_TYPE_SELECT, label: "Select" }, - { value: PERMIT_TYPES.TROS, label: getPermitTypeName(PERMIT_TYPES.TROS) }, - { value: PERMIT_TYPES.TROW, label: getPermitTypeName(PERMIT_TYPES.TROW) }, +export const ALL_PERMIT_TYPE_CHOOSE_FROM_OPTIONS: PermitTypeChooseFromItem[] = [ + { + value: PERMIT_CATEGORIES.TERM, + label: getPermitCategoryName(PERMIT_CATEGORIES.TERM), + items: TERM_PERMIT_LIST.map((permitType: PermitType) => ({ + value: permitType, + label: getPermitTypeShortName(permitType), + })), + }, + /* TODO uncomment these when required */ + // { + // value: PERMIT_CATEGORIES.SINGLE_TRIP, + // label: getPermitCategoryName(PERMIT_CATEGORIES.SINGLE_TRIP), + // items: SINGLE_TRIP_PERMIT_LIST.map((permitType: PermitType) => ({ + // value: permitType, + // label: getPermitTypeShortName(permitType), + // })), + // }, + // { + // value: PERMIT_CATEGORIES.NON_RESIDENT, + // label: getPermitCategoryName(PERMIT_CATEGORIES.NON_RESIDENT), + // items: NON_RESIDENT_PERMIT_LIST.map((permitType: PermitType) => ({ + // value: permitType, + // label: getPermitTypeShortName(permitType), + // })), + // }, + // { + // value: PERMIT_TYPES.MFP, + // label: getPermitTypeShortName(PERMIT_TYPES.MFP), + // }, ]; +export interface PermitTypeChooseFromItem { + value: PermitType | PermitCategory; + label: string; + items?: PermitTypeChooseFromItem[]; + category?: string; +} + export const BASE_DAYS_IN_YEAR = 365; export const COMMON_MIN_DURATION = 30; export const TERM_DURATION_INTERVAL_DAYS = 30; diff --git a/frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.scss b/frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.scss deleted file mode 100644 index 3d5d43677..000000000 --- a/frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.scss +++ /dev/null @@ -1,27 +0,0 @@ -@use "../../../../../../common/components/form/CustomFormComponents"; -@import "../../../../../../themes/orbcStyles"; - -@include CustomFormComponents.custom-form-component(".select-permit-type__input"); - -.select-permit-type { - &#{&} { - font-size: 1rem; - color: $bc-black; - margin: 0; - } - - & &__label { - font-weight: bold; - margin-bottom: 0.5rem; - } - - & &__input { - background-color: $white; - width: 15.25rem; - margin-bottom: 0; - } - - & &__menu { - width: calc(100% - 10px); - } -} diff --git a/frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.tsx b/frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.tsx deleted file mode 100644 index 2a1cce220..000000000 --- a/frontend/src/features/permits/pages/Application/components/dashboard/SelectPermitType.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { - FormControl, - FormLabel, - Select, - SelectChangeEvent, -} from "@mui/material"; - -import "./SelectPermitType.scss"; -import { Optional } from "../../../../../../common/types/common"; - -export const SelectPermitType = ({ - value, - label, - onChange, - menuItems, -}: { - value: string; - label: string; - onChange: (event: SelectChangeEvent) => void; - menuItems: Optional; -}) => ( - - {label} - - - -); diff --git a/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.scss b/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.scss index 10c6665ef..5a6b9fade 100644 --- a/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.scss +++ b/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.scss @@ -1,8 +1,132 @@ +@import "../../../../../../themes/orbcStyles.scss"; + +$select-width: 347px; + .start-application-action { - display: flex; - flex-direction: row; - align-items: flex-end; - width: 100%; + & &__label { + font-weight: bold; + } + + &__control { + display: flex; + justify-content: space-between; + align-items: stretch; + padding-top: .5rem; + } + + & &__input { + display: flex; + justify-content: space-between; + align-items: center; + background-color: $white; + padding: .5rem .75rem; + white-space: nowrap; + overflow: hidden; + border: 2px solid; + border-color: $bc-text-box-border-grey; + color: $bc-black; + margin-bottom: 0; + width: $select-width; + + &:hover { + background-color: $white; + } + + &:active, &:focus { + border-color: $border-blue; + outline: none; + } + + + &--error { + border-color: $bc-red; + } + + &--open { + border-radius: 4px 4px 0px 0px; + border-bottom: none; + border-color: $border-blue; + // prevent input text shifting when menu is open + padding-top: 6px; + } + + } + &__input-text { + text-align: left; + width: 40ch; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__input-tooltip { + max-width: fit-content; + } + + &__menu-list { + &.MuiList-root { + padding: 0; + + & > * { + border-top: 1px solid $bc-border-grey; + } + } + } + + &__menu-item:not(:last-child) { + &.MuiButtonBase-root { + border-bottom: 1px solid $bc-border-grey; + } + } + + &__menu-container { + &.MuiPaper-root { + border-radius: 0; + box-shadow: 0 0.5rem 0.5rem -0.2rem #00000029; + // prevent menu from being wider than the input element itself + width: calc($select-width - 4px); + transition-duration: 0ms; + } + + &--open { + &.MuiPaper-root { + border: 2px solid; + border-top: none; + border-radius: 0px 0px 2px 2px; + border-color: $border-blue; + } + } + } + + // NESTED MENUS + + &__nested-menu-list { + &.MuiList-root.MuiMenu-list { + padding: 0; + max-height: none; + } + } + + + &__nested-menu-item:not(:last-child) { + &.MuiButtonBase-root { + border-bottom: 1px solid $bc-border-grey; + } + } + + &__nested-menu-container { + &.MuiPaper-root { + border-radius: 4px; + box-shadow: $bc-shadow; + width: calc($select-width - 4px); + transition-duration: 0ms; + border: 2px solid; + border-color: $border-blue; + margin-left: 8px; + } + } + + // SUBMIT BUTTON & &__btn { margin-left: 1.5rem; @@ -11,12 +135,22 @@ font-size: 1rem; font-weight: bold; } + + &__error-msg { + position: absolute; + color: $bc-red + } } -@media (width < 1200px) { - .start-application-action { - flex-direction: column; - align-items: flex-start; +@media (width < 600px) { + .start-application-action { + &__control { + flex-direction: column; + } + + & &__input { + width: 100%; + } & &__btn { margin-left: 0; diff --git a/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.tsx b/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.tsx index 7f2ecebc1..c5de21fe7 100644 --- a/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.tsx +++ b/frontend/src/features/permits/pages/Application/components/dashboard/StartApplicationAction.tsx @@ -1,56 +1,167 @@ -import { useNavigate } from "react-router-dom"; +import { Box, Button, FormLabel, Menu, MenuItem, Tooltip } from "@mui/material"; import { useState } from "react"; -import { Box, MenuItem, SelectChangeEvent, Button } from "@mui/material"; - -import "./StartApplicationAction.scss"; -import { PERMIT_TYPE_CHOOSE_FROM_OPTIONS } from "../../../../constants/constants"; -import { SelectPermitType } from "./SelectPermitType"; +import { useNavigate } from "react-router-dom"; +import { NestedMenuItem } from "mui-nested-menu"; import { APPLICATIONS_ROUTES } from "../../../../../../routes/constants"; -import { DEFAULT_PERMIT_TYPE, EMPTY_PERMIT_TYPE_SELECT, PermitType } from "../../../../types/PermitType"; - -/** - * - * Code taken largely from MUI MenuList Composition - * https://mui.com/material-ui/react-menu/#menulist-composition - * - * - */ +import { + ALL_PERMIT_TYPE_CHOOSE_FROM_OPTIONS, + PermitTypeChooseFromItem, +} from "../../../../constants/constants"; +import { + EMPTY_PERMIT_TYPE_SELECT, + PermitType, + getFormattedPermitTypeName, +} from "../../../../types/PermitType"; +import "./StartApplicationAction.scss"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faChevronDown } from "@fortawesome/free-solid-svg-icons"; + export const StartApplicationAction = () => { const navigate = useNavigate(); - const [chooseFrom, setChooseFrom] = useState( - DEFAULT_PERMIT_TYPE - ); + const [chooseFrom, setChooseFrom] = useState< + PermitType | typeof EMPTY_PERMIT_TYPE_SELECT + >(EMPTY_PERMIT_TYPE_SELECT); + + const [isError, setIsError] = useState(false); - const handleChooseFrom = (event: SelectChangeEvent) => { - setChooseFrom(event.target.value as PermitType | typeof EMPTY_PERMIT_TYPE_SELECT); + const handleChooseFrom = ( + _event: React.MouseEvent, + item: PermitTypeChooseFromItem, + ) => { + setIsError(false); + setChooseFrom(item.value as PermitType); + handleClose(); }; const handleStartButtonClicked = () => { if (chooseFrom !== EMPTY_PERMIT_TYPE_SELECT) { navigate(APPLICATIONS_ROUTES.START_APPLICATION(chooseFrom)); + } else { + setIsError(true); } }; + // Update the structure of menuItems to ensure the callback is applied correctly + const menuItems = ALL_PERMIT_TYPE_CHOOSE_FROM_OPTIONS.map( + (item: PermitTypeChooseFromItem) => ({ + ...item, + callback: (event: React.MouseEvent) => + handleChooseFrom(event, item), + // Correctly set the nested item's callback + items: item?.items?.map((nestedItem) => ({ + ...nestedItem, + callback: (event: React.MouseEvent) => + handleChooseFrom(event, nestedItem), + })), + }), + ); + + const [anchorEl, setAnchorEl] = useState(); + const open = Boolean(anchorEl); + + const handleClick = (e: any) => + setAnchorEl(e.currentTarget as HTMLDivElement); + const handleClose = () => setAnchorEl(null); + + const inputClass = "start-application-action__input"; + const inputOpenClass = "start-application-action__input--open"; + const inputErrorClass = "start-application-action__input--error"; + const menuClass = "start-application-action__menu-container"; + const menuOpenClass = "start-application-action__menu-container--open"; + return ( - ( - - {data.label} - - ))} - /> - - + + Select Permit Type + +
+ + + + + + {menuItems.map((item) => + item.items ? ( + + {item.items.map((nestedItem) => ( + + {nestedItem.label} + + ))} + + ) : ( + + {item.label} + + ), + )} + + + +
+ {isError ? ( + + Select a permit type. + + ) : null}
); }; diff --git a/frontend/src/features/permits/pages/Application/components/pay/ApplicationSummary.tsx b/frontend/src/features/permits/pages/Application/components/pay/ApplicationSummary.tsx index 6e179c9d4..7df79d059 100644 --- a/frontend/src/features/permits/pages/Application/components/pay/ApplicationSummary.tsx +++ b/frontend/src/features/permits/pages/Application/components/pay/ApplicationSummary.tsx @@ -3,10 +3,7 @@ import { Box, Typography } from "@mui/material"; import "./ApplicationSummary.scss"; import { getDefaultRequiredVal } from "../../../../../../common/helpers/util"; import { Nullable } from "../../../../../../common/types/common"; -import { - PermitType, - permitTypeDisplayText, -} from "../../../../types/PermitType"; +import { getPermitTypeName, PermitType } from "../../../../types/PermitType"; export const ApplicationSummary = ({ permitType, @@ -15,7 +12,7 @@ export const ApplicationSummary = ({ permitType?: Nullable; applicationNumber?: Nullable; }) => { - const applicationName = permitTypeDisplayText( + const applicationName = getPermitTypeName( getDefaultRequiredVal("", permitType), ); diff --git a/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx b/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx index 09b10e1f6..960f2b9b3 100644 --- a/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx +++ b/frontend/src/features/permits/pages/Application/tests/ApplicationReview.test.tsx @@ -6,7 +6,7 @@ import { vehicleTypeDisplayText } from "../../../helpers/mappers"; import { VehicleType } from "../../../../manageVehicles/types/Vehicle"; import { getDefaultRequiredVal } from "../../../../../common/helpers/util"; import { calculateFeeByDuration } from "../../../helpers/feeSummary"; -import { permitTypeDisplayText } from "../../../types/PermitType"; +import { getPermitTypeName } from "../../../types/PermitType"; import { DATE_FORMATS, dayjsToLocalStr, @@ -87,7 +87,7 @@ beforeAll(() => { // @ts-ignore window.scrollTo = vi.fn(); listenToMockServer(); - sessionStorage.setItem('onRouteBC.user.companyId', "74"); + sessionStorage.setItem("onRouteBC.user.companyId", "74"); }); beforeEach(() => { @@ -120,7 +120,7 @@ describe("Review and Confirm Application Details", () => { permitType, } = defaultApplicationData; expect(await applicationHeaderTitle()).toHaveTextContent( - permitTypeDisplayText(permitType), + getPermitTypeName(permitType), ); expect(await applicationNumber()).toHaveTextContent( applicationNo as string, @@ -343,7 +343,8 @@ describe("Review and Confirm Application Details", () => { provinceCode, vehicleType, vehicleSubType, - } = defaultApplicationData.permitData.vehicleDetails as PermitVehicleDetails; + } = defaultApplicationData.permitData + .vehicleDetails as PermitVehicleDetails; const unit = getDefaultRequiredVal("", unitNumber); const country = formatCountry(countryCode); const province = formatProvince(countryCode, provinceCode); @@ -415,7 +416,9 @@ describe("Review and Confirm Application Details", () => { defaultApplicationData.permitType, defaultApplicationData.permitData.permitDuration, )}`; - const permitTypeStr = permitTypeDisplayText(defaultApplicationData.permitType); + const permitTypeStr = getPermitTypeName( + defaultApplicationData.permitType, + ); expect(await feeSummaryPermitType()).toHaveTextContent(permitTypeStr); expect(await feeSummaryPrice()).toHaveTextContent(`$${feeSummary}.00`); expect(await feeSummaryTotal()).toHaveTextContent(`$${feeSummary}.00`); diff --git a/frontend/src/features/permits/pages/Refund/RefundPage.tsx b/frontend/src/features/permits/pages/Refund/RefundPage.tsx index cb1dd91bb..6ab9f63cf 100644 --- a/frontend/src/features/permits/pages/Refund/RefundPage.tsx +++ b/frontend/src/features/permits/pages/Refund/RefundPage.tsx @@ -14,7 +14,7 @@ import { } from "@mui/material"; import "./RefundPage.scss"; -import { PermitType, permitTypeDisplayText } from "../../types/PermitType"; +import { getPermitTypeName, PermitType } from "../../types/PermitType"; import { RefundFormData } from "./types/RefundFormData"; import { requiredMessage } from "../../../../common/helpers/validationMessages"; import { getErrorMessage } from "../../../../common/components/form/CustomFormComponents"; @@ -388,7 +388,7 @@ export const RefundPage = ({
- {permitTypeDisplayText(permitType)} + {getPermitTypeName(permitType)}
{permitActionText(permitAction)} Permit #: diff --git a/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx b/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx index ee7c06cd2..af5f3e07e 100644 --- a/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx +++ b/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx @@ -3,18 +3,14 @@ import { Box, Typography } from "@mui/material"; import "./VoidPermitHeader.scss"; import { Permit } from "../../../types/permit"; import { CompanyBanner } from "../../../../../common/components/banners/CompanyBanner"; -import { permitTypeDisplayText } from "../../../types/PermitType"; +import { getPermitTypeName } from "../../../types/PermitType"; import { Nullable } from "../../../../../common/types/common"; import { DATE_FORMATS, toLocal, } from "../../../../../common/helpers/formatDate"; -export const VoidPermitHeader = ({ - permit, -}: { - permit: Nullable; -}) => { +export const VoidPermitHeader = ({ permit }: { permit: Nullable }) => { return permit ? (
- {permitTypeDisplayText(permit.permitType)} + {getPermitTypeName(permit.permitType)} diff --git a/frontend/src/features/permits/types/PermitCategory.ts b/frontend/src/features/permits/types/PermitCategory.ts new file mode 100644 index 000000000..cb76673df --- /dev/null +++ b/frontend/src/features/permits/types/PermitCategory.ts @@ -0,0 +1,51 @@ +import { + NON_RESIDENT_PERMIT_LIST, + PermitType, + SINGLE_TRIP_PERMIT_LIST, + TERM_PERMIT_LIST, +} from "./PermitType"; + +export const PERMIT_CATEGORIES = { + TERM: "TERM", + SINGLE_TRIP: "SINGLE_TRIP", + NON_RESIDENT: "NON_RESIDENT", +}; + +export type PermitCategory = + (typeof PERMIT_CATEGORIES)[keyof typeof PERMIT_CATEGORIES]; + +/** + * Returns the name of the permit category. + * @param permitCategory String that represents the permit category + * @returns Name of the permit category, or empty string if no mapping exists for permit category + */ +export const getPermitCategoryName = (permitCategory: PermitCategory) => { + switch (permitCategory) { + case PERMIT_CATEGORIES.TERM: + return "Term"; + case PERMIT_CATEGORIES.SINGLE_TRIP: + return "Single Trip"; + case PERMIT_CATEGORIES.NON_RESIDENT: + return "Non-Resident"; + default: + return ""; + } +}; + +/** + * Returns the permit category for the given permit type. + * @param permitType String that represents the permit type + * @returns Name of the permit category, or empty string if no mapping exists for permit category + */ +export const getPermitCategory = (permitType: PermitType) => { + if (TERM_PERMIT_LIST.includes(permitType)) { + return PERMIT_CATEGORIES.TERM; + } + if (SINGLE_TRIP_PERMIT_LIST.includes(permitType)) { + return PERMIT_CATEGORIES.SINGLE_TRIP; + } + if (NON_RESIDENT_PERMIT_LIST.includes(permitType)) { + return PERMIT_CATEGORIES.NON_RESIDENT; + } + return ""; +}; diff --git a/frontend/src/features/permits/types/PermitType.ts b/frontend/src/features/permits/types/PermitType.ts index 1d2bb9341..a85676631 100644 --- a/frontend/src/features/permits/types/PermitType.ts +++ b/frontend/src/features/permits/types/PermitType.ts @@ -1,36 +1,74 @@ import { Nullable } from "../../../common/types/common"; +import { getPermitCategory, getPermitCategoryName } from "./PermitCategory"; export const PERMIT_TYPES = { - EPTOP: "EPTOP", + /* TERM */ + // Term Oversize + TROS: "TROS", + // Term Overweight + TROW: "TROW", + // Highway Crossing HC: "HC", - LCV: "LCV", - MFP: "MFP", - NRQBS: "NRQBS", - NRQCL: "NRQCL", - NRQCV: "NRQCV", - NRQFT: "NRQFT", - NRQFV: "NRQFV", - NRQXP: "NRQXP", - NRSBS: "NRSBS", - NRSCL: "NRSCL", - NRSCV: "NRSCV", - NRSFT: "NRSFT", - NRSFV: "NRSFV", - NRSXP: "NRSXP", - RIG: "RIG", - STOL: "STOL", - STOS: "STOS", + // Axle Overweight + + /* SINGLE TRIP */ + // Extra Provincial Temp Operating Permit + EPTOP: "EPTOP", + // Single Trip Overweight STOW: "STOW", + // Single Trip Oversize + STOS: "STOS", + // Single Trip Oversize Overweight STWS: "STWS", - TRAX: "TRAX", - TROS: "TROS", - TROW: "TROW", + // Empty - Single Trip Over Length 27.5 + STOL: "STOL", + // Rig Move + RIG: "RIG", + // Increased GVW + IGVW: "IGVW", + + /* NON RESIDENT */ + // Quarterly ICBC Basic Insurance (FR) + QRFR: "QRFR", + // Quarterly Non-Resident + QNRBS: "QNRBS", + // Single Trip ICBC Basic Insurance (FR) + STFR: "STFR", + // Single Trip Non-Resident + NRSCV: "NRSCV", + + /* MOTIVE FUEL USER PERMIT */ + MFP: "MFP", } as const; export type PermitType = (typeof PERMIT_TYPES)[keyof typeof PERMIT_TYPES]; export const DEFAULT_PERMIT_TYPE = PERMIT_TYPES.TROS; -export const EMPTY_PERMIT_TYPE_SELECT = "select"; +export const EMPTY_PERMIT_TYPE_SELECT = "Select"; + +export const TERM_PERMIT_LIST: PermitType[] = [ + PERMIT_TYPES.TROS, + PERMIT_TYPES.TROW, + /* TODO uncomment this when required */ + // PERMIT_TYPES.HC, +]; + +export const SINGLE_TRIP_PERMIT_LIST: PermitType[] = [ + PERMIT_TYPES.STOL, + PERMIT_TYPES.EPTOP, + PERMIT_TYPES.IGVW, + PERMIT_TYPES.STOS, + PERMIT_TYPES.STWS, + PERMIT_TYPES.STOW, + PERMIT_TYPES.RIG, +]; + +export const NON_RESIDENT_PERMIT_LIST: PermitType[] = [ + PERMIT_TYPES.QNRBS, + PERMIT_TYPES.QRFR, + PERMIT_TYPES.NRSCV, + PERMIT_TYPES.STFR, +]; /** * Returns the name/description of the permit type. @@ -39,73 +77,109 @@ export const EMPTY_PERMIT_TYPE_SELECT = "select"; */ export const getPermitTypeName = (permitType?: Nullable) => { switch (permitType) { - case PERMIT_TYPES.EPTOP: - return "Extra-Provincial Temporary Operating"; + /* TERM */ + case PERMIT_TYPES.TROS: + return "Term Oversize"; + case PERMIT_TYPES.TROW: + return "Term Overweight"; case PERMIT_TYPES.HC: return "Highway Crossing"; - case PERMIT_TYPES.LCV: - return "Long Combination Vehicle"; - case PERMIT_TYPES.MFP: - return "Motive Fuel User"; - case PERMIT_TYPES.NRQBS: - return "Quarterly Non Resident Reg. / Ins. - Bus"; - case PERMIT_TYPES.NRQCL: - return "Non Resident Quarterly Conditional License"; - case PERMIT_TYPES.NRQCV: - return "Quarterly Non Resident Reg. / Ins. - Comm Vehicle"; - case PERMIT_TYPES.NRQFT: - return "Non Resident Quarterly Farm Tractor"; - case PERMIT_TYPES.NRQFV: - return "Quarterly Non Resident Reg. / Ins. - Farm Vehicle"; - case PERMIT_TYPES.NRQXP: - return "Non Resident Quarterly X Plated"; - case PERMIT_TYPES.NRSBS: - return "Single Trip Non-Resident Registration / Insurance - Buses"; - case PERMIT_TYPES.NRSCL: - return "Non Resident Single Trip Conditional License"; - case PERMIT_TYPES.NRSCV: - return "Single Trip Non-Resident Reg. / Ins. - Commercial Vehicle"; - case PERMIT_TYPES.NRSFT: - return "Non Resident Farm Tractor Single Trip"; - case PERMIT_TYPES.NRSFV: - return "Single Trip Non-Resident Reg. / Ins. - Farm Vehicle"; - case PERMIT_TYPES.NRSXP: - return "Non Resident Single Trip X Plated Vehicle"; - case PERMIT_TYPES.RIG: - return "Rig Move"; - case PERMIT_TYPES.STOL: - return "Single Trip Over Length"; + + /* SINGLE TRIP */ case PERMIT_TYPES.STOS: return "Single Trip Oversize"; - case PERMIT_TYPES.STOW: - return "Single Trip Over Weight"; case PERMIT_TYPES.STWS: return "Single Trip Overweight Oversize"; - case PERMIT_TYPES.TRAX: - return "Term Axle Overweight"; - case PERMIT_TYPES.TROS: - return "Term Oversize"; - case PERMIT_TYPES.TROW: - return "Term Overweight"; + case PERMIT_TYPES.STOW: + return "Single Trip Over Weight"; + case PERMIT_TYPES.EPTOP: + return "Extra-Provincial Temporary Operating"; + case PERMIT_TYPES.STOL: + return "Single Trip Over Length"; + case PERMIT_TYPES.RIG: + return "Rig Move"; + case PERMIT_TYPES.IGVW: + return "Increased GVW"; + + /* NON-RESIDENT */ + case PERMIT_TYPES.NRSCV: + return "Single Trip Non-Resident"; + case PERMIT_TYPES.QNRBS: + return "Quarterly Non-Resident"; + case PERMIT_TYPES.QRFR: + return "Quarterly ICBC Basic Insurance (FR)"; + case PERMIT_TYPES.STFR: + return "Single Trip ICBC Basic Insurance (FR)"; + + /* MOTIVE FUEL USER PERMIT */ + case PERMIT_TYPES.MFP: + return "Motive Fuel User Permit"; + default: return ""; } }; /** - * Gets display text for permit type. - * @param permitType Permit type (eg. TROS, STOS, etc) - * @returns display text for the permit type + * Returns the shortened name/description of the permit type. + * @param permitType String (if any) that represents the permit type + * @returns Short name/description of the permit type, or empty string if no mapping exists for permit type */ -export const permitTypeDisplayText = (permitType?: Nullable) => { +export const getPermitTypeShortName = (permitType?: Nullable) => { switch (permitType) { + /* TERM */ case PERMIT_TYPES.TROS: - return "Oversize: Term"; + return "Oversize"; + case PERMIT_TYPES.TROW: + return "Overweight"; + case PERMIT_TYPES.HC: + return "Highway Crossing"; + + /* SINGLE TRIP */ + case PERMIT_TYPES.EPTOP: + return "Extra-Provincial Temporary Operating Permit"; + case PERMIT_TYPES.STOW: + return "Overweight"; case PERMIT_TYPES.STOS: - return "Oversize: Single Trip"; + return "Oversize"; + case PERMIT_TYPES.STWS: + return "Oversized Overweight"; + case PERMIT_TYPES.STOL: + return "Empty - Length Over 27.5 m"; + case PERMIT_TYPES.RIG: + return "Rig Move"; + case PERMIT_TYPES.IGVW: + return "Increased GVW"; + + /* NON RESIDENT */ + case PERMIT_TYPES.QRFR: + return "Quarterly ICBC Basic Insurance (FR)"; + case PERMIT_TYPES.QNRBS: + return "Quarterly"; + case PERMIT_TYPES.STFR: + return "Single Trip ICBC Basic Insurance (FR)"; + case PERMIT_TYPES.NRSCV: + return "Single Trip"; + + /* MOTIVE FUEL USER PERMIT */ + case PERMIT_TYPES.MFP: + return "Motive Fuel User Permit"; + default: - return getPermitTypeName(permitType); + return ""; + } +}; + +/** + * Gets formatted Permit Type name as "PermitCategory > PermitType". Used in the Select Permit Type dropdown + * @param permitType Permit type (eg. TROS, STOS, etc) + * @returns formatted display text for the permit category type + */ +export const getFormattedPermitTypeName = (permitType: PermitType) => { + if (permitType === PERMIT_TYPES.MFP) { + return getPermitTypeName(PERMIT_TYPES.MFP); } + return `${getPermitCategoryName(getPermitCategory(permitType))} > ${getPermitTypeShortName(permitType)}`; }; /** @@ -114,7 +188,10 @@ export const permitTypeDisplayText = (permitType?: Nullable) => { * @returns true if string is a valid permit type, or false otherwise */ export const isPermitTypeValid = (permitType?: Nullable) => { - return permitType && (Object.values(PERMIT_TYPES) as string[]).includes(permitType.toUpperCase()); + return ( + permitType && + (Object.values(PERMIT_TYPES) as string[]).includes(permitType.toUpperCase()) + ); }; /** @@ -123,6 +200,5 @@ export const isPermitTypeValid = (permitType?: Nullable) => { * @returns Whether or not the permit of that type is considered a term permit */ export const isTermPermitType = (permitType: PermitType) => { - return permitType === PERMIT_TYPES.TROS - || permitType === PERMIT_TYPES.TROW; + return permitType === PERMIT_TYPES.TROS || permitType === PERMIT_TYPES.TROW; }; diff --git a/frontend/src/themes/orbcStyles.scss b/frontend/src/themes/orbcStyles.scss index 128161cdc..fe51cdc8a 100644 --- a/frontend/src/themes/orbcStyles.scss +++ b/frontend/src/themes/orbcStyles.scss @@ -36,6 +36,7 @@ $blue: #0000ff; $button-hover: #2d5992; $disabled-colour: #b5c0cf; $focus-blue: #3b99fc; +$border-blue: #5697F5; $shadow-colour: #00000029; $white: #ffffff; From e658f381d2cdb7f405cdea1ed3abfbd538b70c8b Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Mon, 21 Oct 2024 10:56:48 -0700 Subject: [PATCH 45/58] ORV2-2870 - Regression: Editing year on vehicle will frequently save a random year when saving (#1640) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../components/form/CustomFormComponents.tsx | 6 ++++++ .../subFormComponents/CustomOutlinedInput.tsx | 8 ++++++-- .../disableMouseWheelInputOnNumberField.ts | 18 ++++++++++++++++++ .../components/form/PowerUnitForm.tsx | 4 +++- .../components/form/TrailerForm.tsx | 6 ++++-- .../form/VehicleDetails/VehicleDetails.tsx | 2 ++ 6 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 frontend/src/common/helpers/disableMouseWheelInputOnNumberField.ts diff --git a/frontend/src/common/components/form/CustomFormComponents.tsx b/frontend/src/common/components/form/CustomFormComponents.tsx index f62284bc4..9a45225b8 100644 --- a/frontend/src/common/components/form/CustomFormComponents.tsx +++ b/frontend/src/common/components/form/CustomFormComponents.tsx @@ -24,6 +24,8 @@ export interface CustomFormComponentProps { className?: string; disabled?: boolean; readOnly?: boolean; + onFocus?: (event: React.FocusEvent) => void; + onWheel?: (event: React.WheelEvent) => void; } /** @@ -91,6 +93,8 @@ export const CustomFormComponent = ({ className, disabled, readOnly, + onFocus, + onWheel, }: CustomFormComponentProps): JSX.Element => { const { control, @@ -137,6 +141,8 @@ export const CustomFormComponent = ({ inputType={inputType} disabled={disabled} readOnly={readOnly} + onFocus={onFocus} + onWheel={onWheel} /> ); case "textarea": diff --git a/frontend/src/common/components/form/subFormComponents/CustomOutlinedInput.tsx b/frontend/src/common/components/form/subFormComponents/CustomOutlinedInput.tsx index a77065ece..052e087a5 100644 --- a/frontend/src/common/components/form/subFormComponents/CustomOutlinedInput.tsx +++ b/frontend/src/common/components/form/subFormComponents/CustomOutlinedInput.tsx @@ -8,6 +8,7 @@ import { import { ORBC_FormTypes } from "../../../types/common"; import "./CustomOutlinedInput.scss"; +import React from "react"; /** * Properties of the onrouteBC customized OutlineInput MUI component @@ -21,6 +22,8 @@ export interface CustomOutlinedInputProps { inputType?: "number"; // currently only support number, add "date", "email" and other types later disabled?: boolean; readOnly?: boolean; + onFocus?: (event: React.FocusEvent) => void; + onWheel?: (event: React.WheelEvent) => void; } /** @@ -50,8 +53,7 @@ export const CustomOutlinedInput = ( updatedInputProps["pattern"] = "[0-9]*"; } - const customInputClassName = - `custom-input ${props.disabled ? "custom-input--disabled" : ""} ${props.invalid ? "custom-input--invalid" : ""}`; + const customInputClassName = `custom-input ${props.disabled ? "custom-input--disabled" : ""} ${props.invalid ? "custom-input--invalid" : ""}`; return ( ( readOnly={props.readOnly} className={customInputClassName} {...register(props.name, props.rules)} + onFocus={props.onFocus} + onWheel={props.onWheel} /> ); }; diff --git a/frontend/src/common/helpers/disableMouseWheelInputOnNumberField.ts b/frontend/src/common/helpers/disableMouseWheelInputOnNumberField.ts new file mode 100644 index 000000000..548842eca --- /dev/null +++ b/frontend/src/common/helpers/disableMouseWheelInputOnNumberField.ts @@ -0,0 +1,18 @@ +import React from "react"; + +// Prevent mouse wheel from changing the value on fields where inputType: "number" +export const disableMouseWheelInputOnNumberField = ( + event: React.FocusEvent, +) => { + const { target } = event; + + const handleWheel = (event: WheelEvent) => event.preventDefault(); + + target.addEventListener("wheel", handleWheel, { + passive: false, + }); + + target.addEventListener("blur", () => + target.removeEventListener("wheel", handleWheel), + ); +}; diff --git a/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx b/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx index 9515bd2be..62b1216f9 100644 --- a/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx +++ b/frontend/src/features/manageVehicles/components/form/PowerUnitForm.tsx @@ -29,6 +29,7 @@ import { invalidYearMin, requiredMessage, } from "../../../../common/helpers/validationMessages"; +import { disableMouseWheelInputOnNumberField } from "../../../../common/helpers/disableMouseWheelInputOnNumberField"; const FEATURE = "power-unit"; @@ -173,6 +174,7 @@ export const PowerUnitForm = ({ - + { navigate(VEHICLES_ROUTES.TRAILER_TAB); }; - + const saveButtonText = isEditMode ? "Save" : "Add To Inventory"; return ( @@ -161,6 +162,7 @@ export const TrailerForm = ({
- + ) : null} - {onReject ? ( - - ) : null} - - {onApprove ? ( - - ) : null} + {reviewContext === PERMIT_REVIEW_CONTEXTS.QUEUE && ( + <> + + + + )} ); }; diff --git a/frontend/src/features/queue/apiManager/queueAPI.ts b/frontend/src/features/queue/apiManager/queueAPI.ts index 0415d85dc..a45a0c5e0 100644 --- a/frontend/src/features/queue/apiManager/queueAPI.ts +++ b/frontend/src/features/queue/apiManager/queueAPI.ts @@ -27,7 +27,7 @@ export const getApplicationsInQueue = async ( { applicationsInQueueOnly: true, getStaffQueue, - }, + }, companyId, ); }; @@ -82,7 +82,7 @@ export const updateApplicationQueueStatus = async ({ // Conditionally include the comment property if it is given as an argument and not an empty string if (comment && comment.trim() !== "") { - data.comment = [comment]; + data.comment = comment; } const response = await httpPOSTRequest( diff --git a/frontend/src/features/queue/components/ApplicationInQueueReview.tsx b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx index 075f8de46..f13a0fe38 100644 --- a/frontend/src/features/queue/components/ApplicationInQueueReview.tsx +++ b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx @@ -1,26 +1,23 @@ import { useEffect, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; - -import "./ApplicationInQueueReview.scss"; -import { QueueBreadcrumb } from "./QueueBreadcrumb"; -import { PERMIT_REVIEW_CONTEXTS } from "../../permits/types/PermitReviewContext"; +import { getDefaultRequiredVal } from "../../../common/helpers/util"; +import { Nullable } from "../../../common/types/common"; +import { APPLICATION_STEPS, IDIR_ROUTES } from "../../../routes/constants"; import { useCompanyInfoDetailsQuery } from "../../manageProfile/apiManager/hooks"; import { usePowerUnitSubTypesQuery } from "../../manageVehicles/hooks/powerUnits"; import { useTrailerSubTypesQuery } from "../../manageVehicles/hooks/trailers"; import { calculateFeeByDuration } from "../../permits/helpers/feeSummary"; import { PermitReview } from "../../permits/pages/Application/components/review/PermitReview"; import { Application } from "../../permits/types/application"; +import { PERMIT_REVIEW_CONTEXTS } from "../../permits/types/PermitReviewContext"; import { DEFAULT_PERMIT_TYPE } from "../../permits/types/PermitType"; import { useFetchSpecialAuthorizations } from "../../settings/hooks/specialAuthorizations"; -import { Nullable } from "../../../common/types/common"; -import { getDefaultRequiredVal } from "../../../common/helpers/util"; -import { - useApproveApplicationInQueueMutation, - useRejectApplicationInQueueMutation, -} from "../hooks/hooks"; - -import { APPLICATION_STEPS, IDIR_ROUTES } from "../../../routes/constants"; +import { useUpdateApplicationInQueueStatus } from "../hooks/hooks"; +import { CASE_ACTIVITY_TYPES } from "../types/CaseActivityType"; +import "./ApplicationInQueueReview.scss"; +import { QueueBreadcrumb } from "./QueueBreadcrumb"; +import { RejectApplicationModal } from "./RejectApplicationModal"; export const ApplicationInQueueReview = ({ applicationData, @@ -28,6 +25,7 @@ export const ApplicationInQueueReview = ({ applicationData?: Nullable; }) => { const companyId = getDefaultRequiredVal(0, applicationData?.companyId); + const applicationId = getDefaultRequiredVal("", applicationData?.permitId); const { data: specialAuth } = useFetchSpecialAuthorizations(companyId); const isNoFeePermitType = Boolean(specialAuth?.noFeeType); @@ -60,48 +58,46 @@ export const ApplicationInQueueReview = ({ const isSuccess = (status?: number) => status === 201; const { - mutateAsync: approveApplication, - data: approveApplicationResponse, - isPending: approveApplicationMutationPending, - } = useApproveApplicationInQueueMutation(); + mutateAsync: updateApplication, + data: updateApplicationResponse, + isPending: updateApplicationMutationPending, + } = useUpdateApplicationInQueueStatus(); const handleApprove = async (): Promise => { setHasAttemptedSubmission(true); - if (!allConfirmed) return; - await approveApplication({ - applicationId: applicationData?.permitId, + await updateApplication({ + applicationId, companyId, + caseActivityType: CASE_ACTIVITY_TYPES.APPROVED, }); }; - useEffect(() => { - if (isSuccess(approveApplicationResponse?.status)) { - navigate(IDIR_ROUTES.STAFF_HOME); - } - }, [approveApplicationResponse, navigate]); + const [showRejectApplicationModal, setShowRejectApplicationModal] = + useState(false); - const { - mutateAsync: rejectApplication, - data: rejectApplicationResponse, - isPending: rejectApplicationMutationPending, - } = useRejectApplicationInQueueMutation(); + const handleRejectButton = () => { + setShowRejectApplicationModal(true); + }; - const handleReject = async (): Promise => { + const handleReject = async (comment: string): Promise => { setHasAttemptedSubmission(true); - if (!allConfirmed) return; - await rejectApplication({ - applicationId: applicationData?.permitId, + await updateApplication({ + applicationId, companyId, + caseActivityType: CASE_ACTIVITY_TYPES.REJECTED, + comment, }); }; + const updateApplicationResponseStatus = updateApplicationResponse?.status; + useEffect(() => { - if (isSuccess(rejectApplicationResponse?.status)) { + if (isSuccess(updateApplicationResponseStatus)) { navigate(IDIR_ROUTES.STAFF_HOME); } - }, [rejectApplicationResponse, navigate]); + }, [updateApplicationResponseStatus, navigate]); useEffect(() => { window.scrollTo(0, 0); @@ -130,10 +126,9 @@ export const ApplicationInQueueReview = ({ companyInfo={companyInfo} contactDetails={applicationData?.permitData?.contactDetails} onEdit={handleEdit} - onApprove={handleApprove} - approveApplicationMutationPending={approveApplicationMutationPending} - onReject={handleReject} - rejectApplicationMutationPending={rejectApplicationMutationPending} + handleApproveButton={handleApprove} + updateApplicationMutationPending={updateApplicationMutationPending} + handleRejectButton={handleRejectButton} allConfirmed={allConfirmed} setAllConfirmed={setAllConfirmed} hasAttemptedCheckboxes={hasAttemptedSubmission} @@ -147,6 +142,14 @@ export const ApplicationInQueueReview = ({ calculatedFee={fee} /> + {showRejectApplicationModal && ( + setShowRejectApplicationModal(false)} + onConfirm={handleReject} + isPending={updateApplicationMutationPending} + /> + )}
); }; diff --git a/frontend/src/features/queue/components/RejectApplicationModal.scss b/frontend/src/features/queue/components/RejectApplicationModal.scss new file mode 100644 index 000000000..4fe091114 --- /dev/null +++ b/frontend/src/features/queue/components/RejectApplicationModal.scss @@ -0,0 +1,97 @@ +@import "../../../themes/orbcStyles.scss"; + +.reject-application-modal { + & &__container { + width: 100%; + display: flex; + flex-direction: column; + } + + &__header { + padding: 2rem 1.5rem; + display: flex; + flex-direction: row; + align-items: center; + background-color: $bc-background-light-grey; + } + + &__icon { + .icon { + color: $bc-messages-red-text; + height: 2rem; + } + } + + &__title { + font-weight: 600; + font-size: 1.5rem; + margin-left: 0.5em; + color: $bc-messages-red-text; + } + + &__body { + padding: 1.5rem; + } + + .reject-application-form { + width: 100%; + + &__label { + font-weight: bold; + font-size: 1rem; + color: $bc-black; + } + + .custom-form-control { + margin: 0; + } + + .custom-text-area { + font-family: $default-font; + } + } + + &__text { + padding-bottom: 1.5rem; + } + + &__footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + padding: 0 1.5rem 1.5rem 1.5rem; + + } + & &__button { + &--cancel { + margin-right: 1.5rem; + cursor: pointer; + background-color: $bc-background-light-grey; + color: $bc-black; + border: 2px solid $bc-background-light-grey; + box-shadow: none; + + &:hover, &:focus { + background-color: $bc-background-light-grey; + border: 2px solid $bc-border-grey; + box-shadow: none; + } + } + + &--confirm { + color: $white; + background-color: $bc-red; + font-weight: bold; + cursor: pointer; + + &:hover, &:focus { + background-color: $bc-messages-red-text + } + + &:disabled { + background-color: $bc-red-disabled; + color: $white; + } + } + } +} diff --git a/frontend/src/features/queue/components/RejectApplicationModal.tsx b/frontend/src/features/queue/components/RejectApplicationModal.tsx new file mode 100644 index 000000000..09644b1c9 --- /dev/null +++ b/frontend/src/features/queue/components/RejectApplicationModal.tsx @@ -0,0 +1,103 @@ +import { faCircleExclamation } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Button, Dialog } from "@mui/material"; +import { FormProvider, useForm } from "react-hook-form"; +import { CustomFormComponent } from "../../../common/components/form/CustomFormComponents"; +import { requiredMessage } from "../../../common/helpers/validationMessages"; +import "./RejectApplicationModal.scss"; + +export const RejectApplicationModal = ({ + showModal, + onCancel, + onConfirm, + isPending, +}: { + showModal: boolean; + onCancel: () => void; + onConfirm: (comment: string) => void; + isPending: boolean; +}) => { + const formMethods = useForm<{ comment: string }>({ + defaultValues: { + comment: "", + }, + reValidateMode: "onChange", + }); + + const { handleSubmit, getValues } = formMethods; + + const handleCancel = () => onCancel(); + + const handleRejectApplication = () => { + const { comment } = getValues(); + onConfirm(comment); + }; + + const rejectApplicationCommentRules = { + required: { + value: true, + message: requiredMessage(), + }, + }; + + return ( + +
+
+ +
+ + + Application Rejection + +
+ + +
+
+ +
+
+ +
+ + + +
+
+
+ ); +}; diff --git a/frontend/src/features/queue/hooks/hooks.ts b/frontend/src/features/queue/hooks/hooks.ts index 6d88eee4b..8e6ab2701 100644 --- a/frontend/src/features/queue/hooks/hooks.ts +++ b/frontend/src/features/queue/hooks/hooks.ts @@ -1,14 +1,16 @@ -import { useContext } from "react"; -import OnRouteBCContext from "../../../common/authentication/OnRouteBCContext"; -import { IDIRUserRoleType } from "../../../common/authentication/types"; -import { canViewApplicationQueue } from "../helpers/canViewApplicationQueue"; -import { MRT_PaginationState, MRT_SortingState } from "material-react-table"; import { keepPreviousData, useMutation, useQuery, useQueryClient, } from "@tanstack/react-query"; +import { AxiosError } from "axios"; +import { MRT_PaginationState, MRT_SortingState } from "material-react-table"; +import { useContext } from "react"; +import OnRouteBCContext from "../../../common/authentication/OnRouteBCContext"; +import { IDIRUserRoleType } from "../../../common/authentication/types"; +import { Nullable } from "../../../common/types/common"; +import { useTableControls } from "../../permits/hooks/useTableControls"; import { claimApplicationInQueue, getApplicationsInQueue, @@ -16,11 +18,8 @@ import { getUnclaimedApplicationsInQueue, updateApplicationQueueStatus, } from "../apiManager/queueAPI"; -import { Nullable } from "../../../common/types/common"; -import { SnackBarContext } from "../../../App"; -import { CASE_ACTIVITY_TYPES } from "../types/CaseActivityType"; -import { AxiosError } from "axios"; -import { useTableControls } from "../../permits/hooks/useTableControls"; +import { canViewApplicationQueue } from "../helpers/canViewApplicationQueue"; +import { CaseActivityType } from "../types/CaseActivityType"; const QUEUE_QUERY_KEYS_BASE = "queue"; @@ -159,70 +158,17 @@ export const useClaimApplicationInQueueMutation = () => { }); }; -export const useWithdrawApplicationInQueueMutation = () => { - const { invalidate } = useInvalidateApplicationsInQueue(); - const { setSnackBar } = useContext(SnackBarContext); - - return useMutation({ - mutationFn: (applicationId: string) => { - return updateApplicationQueueStatus({ - applicationId, - caseActivityType: CASE_ACTIVITY_TYPES.WITHDRAWN, - }); - }, - onSuccess: () => { - setSnackBar({ - showSnackbar: true, - setShowSnackbar: () => true, - message: "Withdrawn to Applications in Progress", - alertType: "info", - }); - invalidate(); - }, - onError: (err: AxiosError) => err, - }); -}; - -export const useApproveApplicationInQueueMutation = () => { - const { invalidate } = useInvalidateApplicationsInQueue(); - - return useMutation({ - mutationFn: ({ - applicationId, - companyId, - }: { - applicationId: Nullable; - companyId: number; - }) => { - return updateApplicationQueueStatus({ - applicationId, - caseActivityType: CASE_ACTIVITY_TYPES.APPROVED, - companyId, - }); - }, - onSuccess: () => { - invalidate(); - }, - onError: (err: AxiosError) => err, - }); -}; - -export const useRejectApplicationInQueueMutation = () => { +export const useUpdateApplicationInQueueStatus = () => { const { invalidate } = useInvalidateApplicationsInQueue(); return useMutation({ - mutationFn: ({ - applicationId, - companyId, - }: { - applicationId: Nullable; - companyId: number; + mutationFn: (data: { + applicationId: string; + caseActivityType: CaseActivityType; + companyId?: number; + comment?: string; }) => { - return updateApplicationQueueStatus({ - applicationId, - caseActivityType: CASE_ACTIVITY_TYPES.REJECTED, - companyId, - }); + return updateApplicationQueueStatus(data); }, onSuccess: () => { invalidate(); From f8b808e42f3332ce9a644800eec8c35a474b6d97 Mon Sep 17 00:00:00 2001 From: John Fletcher <113134542+john-fletcher-aot@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:07:59 -0700 Subject: [PATCH 48/58] Add GL type and GL code type tables (#1653) --- .../versions/revert/v_46_ddl_revert.sql | 23 ++++ database/mssql/scripts/versions/v_46_ddl.sql | 111 ++++++++++++++++++ database/mssql/test/versions/v_46_1_test.sql | 6 + database/mssql/test/versions/v_46_test.sh | 16 +++ 4 files changed, 156 insertions(+) create mode 100644 database/mssql/scripts/versions/revert/v_46_ddl_revert.sql create mode 100644 database/mssql/scripts/versions/v_46_ddl.sql create mode 100644 database/mssql/test/versions/v_46_1_test.sql create mode 100644 database/mssql/test/versions/v_46_test.sh diff --git a/database/mssql/scripts/versions/revert/v_46_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_46_ddl_revert.sql new file mode 100644 index 000000000..3c2eb6d62 --- /dev/null +++ b/database/mssql/scripts/versions/revert/v_46_ddl_revert.sql @@ -0,0 +1,23 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +BEGIN TRANSACTION +GO + +DROP TABLE [permit].[ORBC_GL_CODE_TYPE] +GO +DROP TABLE [permit].[ORBC_GL_TYPE] +GO +COMMIT + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Reverting addition of GL Type and GL Code Type tables' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (45, @VersionDescription, getutcdate()) diff --git a/database/mssql/scripts/versions/v_46_ddl.sql b/database/mssql/scripts/versions/v_46_ddl.sql new file mode 100644 index 000000000..01b102284 --- /dev/null +++ b/database/mssql/scripts/versions/v_46_ddl.sql @@ -0,0 +1,111 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + +CREATE TABLE [permit].[ORBC_GL_TYPE]( + [GL_TYPE] [char] (6) NOT NULL, + [NAME] [varchar] (20) NOT NULL, + [DESCRIPTION] [varchar] (50) NULL, + [APP_CREATE_TIMESTAMP] [datetime2](7) DEFAULT (getutcdate()), + [APP_CREATE_USERID] [nvarchar](30) DEFAULT (user_name()), + [APP_CREATE_USER_GUID] [char](32) NULL, + [APP_CREATE_USER_DIRECTORY] [nvarchar](30) DEFAULT (user_name()), + [APP_LAST_UPDATE_TIMESTAMP] [datetime2](7) DEFAULT (getutcdate()), + [APP_LAST_UPDATE_USERID] [nvarchar](30) DEFAULT (user_name()), + [APP_LAST_UPDATE_USER_GUID] [char](32) NULL, + [APP_LAST_UPDATE_USER_DIRECTORY] [nvarchar](30) DEFAULT (user_name()), + [CONCURRENCY_CONTROL_NUMBER] [int] NULL, + [DB_CREATE_USERID] [varchar](63) NULL, + [DB_CREATE_TIMESTAMP] [datetime2](7) NULL, + [DB_LAST_UPDATE_USERID] [varchar](63) NULL, + [DB_LAST_UPDATE_TIMESTAMP] [datetime2](7) NULL, + CONSTRAINT [ORBC_GL_TYPE_PK] PRIMARY KEY CLUSTERED +( + [GL_TYPE] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] +GO +CREATE TABLE [permit].[ORBC_GL_CODE_TYPE]( + [GL_CODE_TYPE] [int] IDENTITY(1,1) NOT NULL, + [GL_TYPE] [char] (6) NULL, + [PERMIT_TYPE] [varchar](10) NULL, + [PAYMENT_METHOD_TYPE] [varchar] (15) NULL, + [PAYMENT_CARD_TYPE] [varchar] (5) NULL, + [CLIENT] [char] (3) NOT NULL, + [RESPONSIBILITY] [char] (5) NOT NULL, + [SERVICE_LINE] [char] (5) NOT NULL, + [STOB] [char] (4) NOT NULL, + [PROJECT] [char] (7) NOT NULL, + [LOCATION] [char] (6) NOT NULL, + [FUTURE] [char] (4) NOT NULL, + [CONCURRENCY_CONTROL_NUMBER] [int] NULL, + [DB_CREATE_USERID] [varchar](63) NULL, + [DB_CREATE_TIMESTAMP] [datetime2](7) NULL, + [DB_LAST_UPDATE_USERID] [varchar](63) NULL, + [DB_LAST_UPDATE_TIMESTAMP] [datetime2](7) NULL, + CONSTRAINT [ORBC_GL_CODE_TYPE_PK] PRIMARY KEY CLUSTERED +( + [GL_CODE_TYPE] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] WITH CHECK ADD CONSTRAINT [FK_ORBC_GL_CODE_TYPE_GL_TYPE] FOREIGN KEY([GL_TYPE]) REFERENCES [permit].[ORBC_GL_TYPE] ([GL_TYPE]) +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] CHECK CONSTRAINT [FK_ORBC_GL_CODE_TYPE_GL_TYPE] +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] WITH CHECK ADD CONSTRAINT [FK_ORBC_GL_CODE_TYPE_PERMIT_TYPE] FOREIGN KEY([PERMIT_TYPE]) +REFERENCES [permit].[ORBC_PERMIT_TYPE] ([PERMIT_TYPE]) +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] CHECK CONSTRAINT [FK_ORBC_GL_CODE_TYPE_PERMIT_TYPE] +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] WITH CHECK ADD CONSTRAINT [FK_ORBC_GL_CODE_TYPE_PAYMENT_METHOD_TYPE] FOREIGN KEY([PAYMENT_METHOD_TYPE]) REFERENCES [permit].[ORBC_PAYMENT_METHOD_TYPE] ([PAYMENT_METHOD_TYPE]) +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] CHECK CONSTRAINT [FK_ORBC_GL_CODE_TYPE_PAYMENT_METHOD_TYPE] +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] WITH CHECK ADD CONSTRAINT [FK_ORBC_GL_CODE_TYPE_PAYMENT_CARD_TYPE] FOREIGN KEY([PAYMENT_CARD_TYPE]) REFERENCES [permit].[ORBC_PAYMENT_CARD_TYPE] ([PAYMENT_CARD_TYPE]) +GO +ALTER TABLE [permit].[ORBC_GL_CODE_TYPE] CHECK CONSTRAINT [FK_ORBC_GL_CODE_TYPE_PAYMENT_CARD_TYPE] +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + +INSERT [permit].[ORBC_GL_TYPE] ([GL_TYPE], [NAME], [DESCRIPTION], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES (N'R_GL', N'Revenue GL', NULL, N'dbo', GETUTCDATE(), N'dbo', GETUTCDATE()) +GO +INSERT [permit].[ORBC_GL_TYPE] ([GL_TYPE], [NAME], [DESCRIPTION], [DB_CREATE_USERID], [DB_CREATE_TIMESTAMP], [DB_LAST_UPDATE_USERID], [DB_LAST_UPDATE_TIMESTAMP]) VALUES (N'BAL_GL', N'Balancing GL', NULL, N'dbo', GETUTCDATE(), N'dbo', GETUTCDATE()) +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Add GL Type and GL Code Type tables' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (46, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO + diff --git a/database/mssql/test/versions/v_46_1_test.sql b/database/mssql/test/versions/v_46_1_test.sql new file mode 100644 index 000000000..33a04ed57 --- /dev/null +++ b/database/mssql/test/versions/v_46_1_test.sql @@ -0,0 +1,6 @@ +SET NOCOUNT ON +IF OBJECT_ID('[$(DB_NAME)].[permit].[ORBC_GL_TYPE]', 'U') IS NOT NULL +AND OBJECT_ID('[$(DB_NAME)].[permit].[ORBC_GL_CODE_TYPE]', 'U') IS NOT NULL + SELECT 1 +ELSE + SELECT 0 diff --git a/database/mssql/test/versions/v_46_test.sh b/database/mssql/test/versions/v_46_test.sh new file mode 100644 index 000000000..6e14f91d5 --- /dev/null +++ b/database/mssql/test/versions/v_46_test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Retrieve arguments +source ${SCRIPT_DIR}/utility/getopt.sh +USAGE="-u USER -p PASS -s SERVER -d DATABASE" +parse_options "${USAGE}" ${@} + +# All database tests for database version 46 are run from this shell script. +# TESTS_DIR variable set by the calling test-runner script. + +TEST_46_1_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_46_1_test.sql | xargs) +if [[ $TEST_46_1_RESULT -eq 1 ]]; then + echo "Test 46.1 passed: GL type and GL code type tables exist." +else + echo "******** Test 46.1 failed: Missing either GL type or GL code type table." +fi From f54b0cc9308e240795e87e0a019ab390aa47738f Mon Sep 17 00:00:00 2001 From: John Fletcher <113134542+john-fletcher-aot@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:55:58 -0700 Subject: [PATCH 49/58] feat: Add ability to load sample data from hidden sql directory in local env (#1654) --- database/mssql/scripts/sampledata/.gitignore | 3 ++- database/mssql/scripts/utility/refresh-sample-data.sh | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/database/mssql/scripts/sampledata/.gitignore b/database/mssql/scripts/sampledata/.gitignore index a3e13cd9a..d2ef88958 100644 --- a/database/mssql/scripts/sampledata/.gitignore +++ b/database/mssql/scripts/sampledata/.gitignore @@ -1,2 +1,3 @@ # Ensure the unencrypted TPS test data is kept out of git -tps.* \ No newline at end of file +tps.* +_private_sql/ \ No newline at end of file diff --git a/database/mssql/scripts/utility/refresh-sample-data.sh b/database/mssql/scripts/utility/refresh-sample-data.sh index 01b5b8ee1..712d22d86 100644 --- a/database/mssql/scripts/utility/refresh-sample-data.sh +++ b/database/mssql/scripts/utility/refresh-sample-data.sh @@ -31,4 +31,14 @@ sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE echo "Setting credit account sequence restart to current timestamp (used only for lower environments)" sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -i ${SCRIPT_DIR}/sampledata/permit.ORBC_CREDIT_ACCOUNT_NUMBER_SEQ.sql +if [ -d "${SCRIPT_DIR}/sampledata/_private_sql" ] +then + echo "Running private SQL (non-repository)" + for f in ${SCRIPT_DIR}/sampledata/_private_sql/*.sql; + do + echo "Processing $f file..."; + sqlcmd -C -U ${ORBC_USER} -P "${ORBC_PASS}" -S ${ORBC_SERVER} -d ${ORBC_DATABASE} -i $f + done +fi + echo "Finished loading sample data" From 99e2712fbfa6c0985ab1a7faa095a4c637e23dec Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:12:41 -0700 Subject: [PATCH 50/58] ORV2-2759 - FE: Staff Approve Application in the Queue (#1652) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../common/constants/validation_messages.json | 5 +++ .../src/common/helpers/validationMessages.ts | 3 ++ .../ApplicationsInReviewRowOptions.tsx | 20 ++++------- .../features/permits/helpers/dateSelection.ts | 34 +++++++++++++++---- .../components/review/PermitReview.tsx | 17 ++++++++-- .../components/review/ReviewActions.tsx | 10 +++--- .../review/ReviewPermitDetails.scss | 11 ++++++ .../components/review/ReviewPermitDetails.tsx | 28 ++++++++++----- .../components/ApplicationInQueueReview.tsx | 10 +++++- frontend/src/features/queue/hooks/hooks.ts | 11 +++++- 10 files changed, 112 insertions(+), 37 deletions(-) diff --git a/frontend/src/common/constants/validation_messages.json b/frontend/src/common/constants/validation_messages.json index c33cae906..d5b16f7fa 100644 --- a/frontend/src/common/constants/validation_messages.json +++ b/frontend/src/common/constants/validation_messages.json @@ -31,6 +31,11 @@ "beforeStart": { "defaultMessage": "Expiry cannot be before Start Date" } + }, + "startOrExpiry": { + "past": { + "defaultMessage": "Start Date and/or Permit Expiry Date is in the past." + } } }, "email": { diff --git a/frontend/src/common/helpers/validationMessages.ts b/frontend/src/common/helpers/validationMessages.ts index 5f594e8bc..a820c02f3 100644 --- a/frontend/src/common/helpers/validationMessages.ts +++ b/frontend/src/common/helpers/validationMessages.ts @@ -33,6 +33,9 @@ export const expiryMustBeAfterStart = () => { return validationMessages.date.expiry.beforeStart.defaultMessage; }; +export const pastStartOrExpiryDate = () => + validationMessages.date.startOrExpiry.past.defaultMessage; + export const invalidEmail = () => validationMessages.email.defaultMessage; export const invalidPhoneLength = (min: number, max: number) => { diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx index bb257ea61..228b60c8a 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx @@ -1,14 +1,12 @@ import { useContext, useEffect, useState } from "react"; +import { SnackBarContext } from "../../../../App"; import { OnRouteBCTableRowActions } from "../../../../common/components/table/OnRouteBCTableRowActions"; -import { ApplicationInReviewModal } from "./ApplicationInReviewModal"; -import { useNavigate } from "react-router-dom"; -import { ERROR_ROUTES } from "../../../../routes/constants"; import { useInvalidateApplicationsInQueue, useUpdateApplicationInQueueStatus, } from "../../../queue/hooks/hooks"; import { CASE_ACTIVITY_TYPES } from "../../../queue/types/CaseActivityType"; -import { SnackBarContext } from "../../../../App"; +import { ApplicationInReviewModal } from "./ApplicationInReviewModal"; const PERMIT_ACTION_OPTION_TYPES = { WITHDRAW_APPLICATION: "withdrawApplication", @@ -52,7 +50,6 @@ export const ApplicationsInReviewRowOptions = ({ isInReview: boolean; permitId: string; }) => { - const navigate = useNavigate(); const { invalidate } = useInvalidateApplicationsInQueue(); const [isAIRModalOpen, setIsAIRModalOpen] = useState(false); @@ -65,20 +62,17 @@ export const ApplicationsInReviewRowOptions = ({ const { mutateAsync: updateApplication, data: updateApplicationResponse, - isError: isUpdateApplicationError, error: updateApplicationError, } = useUpdateApplicationInQueueStatus(); + const updateApplicationErrorStatus = updateApplicationError?.response?.status; + useEffect(() => { - if (isUpdateApplicationError) { + if (updateApplicationErrorStatus === 422) { // if the application has already been withdrawn by another user - if (updateApplicationError.response?.status === 422) { - return setIsAIRModalOpen(true); - } - // handle all other errors - navigate(ERROR_ROUTES.UNEXPECTED); + return setIsAIRModalOpen(true); } - }, [isUpdateApplicationError, updateApplicationError]); + }, [updateApplicationErrorStatus]); const isSuccess = (status?: number) => status === 201; const { setSnackBar } = useContext(SnackBarContext); diff --git a/frontend/src/features/permits/helpers/dateSelection.ts b/frontend/src/features/permits/helpers/dateSelection.ts index 4472fa941..0664b8671 100644 --- a/frontend/src/features/permits/helpers/dateSelection.ts +++ b/frontend/src/features/permits/helpers/dateSelection.ts @@ -1,6 +1,9 @@ -import { Dayjs } from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; -import { BASE_DAYS_IN_YEAR, TERM_DURATION_INTERVAL_DAYS } from "../constants/constants"; +import { + BASE_DAYS_IN_YEAR, + TERM_DURATION_INTERVAL_DAYS, +} from "../constants/constants"; import { PERMIT_TYPES, PermitType } from "../types/PermitType"; import { getExpiryDate } from "./permitState"; import { getMostRecentExpiryFromLOAs } from "./permitLOA"; @@ -57,7 +60,7 @@ export const maxDurationForPermitType = (permitType: PermitType) => { * @param permitType Permit type to get duration interval for * @returns Number of days as duration interval for the permit type. */ -export const getDurationIntervalDays = (permitType: PermitType) => { +export const getDurationIntervalDays = (permitType: PermitType) => { switch (permitType) { case PERMIT_TYPES.TROW: return TROW_DURATION_INTERVAL_DAYS; @@ -100,8 +103,10 @@ export const getAvailableDurationOptions = ( const mostRecentLOAExpiry = getMostRecentExpiryFromLOAs(selectedLOAs); if (!mostRecentLOAExpiry) return fullDurationOptions; - return fullDurationOptions - .filter(({ value: durationDays }) => !mostRecentLOAExpiry.isBefore(getExpiryDate(startDate, durationDays))); + return fullDurationOptions.filter( + ({ value: durationDays }) => + !mostRecentLOAExpiry.isBefore(getExpiryDate(startDate, durationDays)), + ); }; /** @@ -121,7 +126,9 @@ export const handleUpdateDurationIfNeeded = ( }[], ) => { const minAllowableDuration = minDurationForPermitType(permitType); - const maxDurationInOptions = Math.max(...durationOptions.map(durationOption => durationOption.value)); + const maxDurationInOptions = Math.max( + ...durationOptions.map((durationOption) => durationOption.value), + ); if (currentDuration > maxDurationInOptions) { if (maxDurationInOptions < minAllowableDuration) { @@ -132,3 +139,18 @@ export const handleUpdateDurationIfNeeded = ( return currentDuration; }; + +/** + * Determine if start date or expiry date of permit applicationare in the past + * @param startDate Start date of the permit + * @param expiryDate Expiry date of the permit + * @returns True if either startDate or expiryDate are in the past + */ +export const isPermitStartOrExpiryDateInPast = ( + startDate: Dayjs, + expiryDate: Dayjs, +) => { + return ( + dayjs().isAfter(startDate, "day") || dayjs().isAfter(expiryDate, "day") + ); +}; diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx index 042e98d78..8d705f728 100644 --- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx @@ -22,6 +22,7 @@ import { ReviewFeeSummary } from "./ReviewFeeSummary"; import { ReviewPermitDetails } from "./ReviewPermitDetails"; import { ReviewPermitLOAs } from "./ReviewPermitLOAs"; import { ReviewVehicleInfo } from "./ReviewVehicleInfo"; +import { isPermitStartOrExpiryDateInPast } from "../../../../helpers/dateSelection"; interface PermitReviewProps { reviewContext: PermitReviewContext; @@ -60,6 +61,14 @@ interface PermitReviewProps { } export const PermitReview = (props: PermitReviewProps) => { + const invalidPermitDates = + props.permitStartDate && props.permitExpiryDate + ? isPermitStartOrExpiryDateInPast( + props.permitStartDate, + props.permitExpiryDate, + ) + : false; + return ( @@ -94,6 +103,7 @@ export const PermitReview = (props: PermitReviewProps) => { showChangedFields={props.showChangedFields} oldStartDate={props.oldFields?.permitData?.startDate} oldDuration={props.oldFields?.permitData?.permitDuration} + showDateErrorBanner={invalidPermitDates} /> { onContinue={props.onContinue} hasToCartButton={props.reviewContext === PERMIT_REVIEW_CONTEXTS.APPLY} onAddToCart={props.onAddToCart} - disableApproveAndRejectButtons={ - props.updateApplicationMutationPending - } handleApproveButton={props.handleApproveButton} handleRejectButton={props.handleRejectButton} + disableApproveButton={ + props.updateApplicationMutationPending || invalidPermitDates + } + disableRejectButton={props.updateApplicationMutationPending} /> diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx index 6737ebd09..e7e51cb0c 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewActions.tsx @@ -14,9 +14,10 @@ export const ReviewActions = ({ onContinue, hasToCartButton, onAddToCart, - disableApproveAndRejectButtons, handleApproveButton, handleRejectButton, + disableApproveButton, + disableRejectButton, }: { reviewContext: PermitReviewContext; onEdit: () => void; @@ -24,9 +25,10 @@ export const ReviewActions = ({ onContinue?: () => Promise; hasToCartButton: boolean; onAddToCart?: () => Promise; - disableApproveAndRejectButtons?: boolean; handleApproveButton?: () => Promise; handleRejectButton?: () => void; + disableApproveButton?: boolean; + disableRejectButton?: boolean; }) => { return ( @@ -88,7 +90,7 @@ export const ReviewActions = ({ color="error" data-testid="reject-btn" onClick={handleRejectButton} - disabled={disableApproveAndRejectButtons} + disabled={disableRejectButton} > Reject @@ -100,7 +102,7 @@ export const ReviewActions = ({ color="primary" data-testid="approve-btn" onClick={handleApproveButton} - disabled={disableApproveAndRejectButtons} + disabled={disableApproveButton} > Approve diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.scss b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.scss index 7e9c9242a..e114b1327 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.scss +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.scss @@ -1,4 +1,5 @@ @use "../../../../../../themes/orbcStyles"; +@import "../../../../../../themes/orbcStyles"; @include orbcStyles.permit-main-box-style(".review-permit-details"); @include orbcStyles.permit-left-box-style(".review-permit-details__header"); @@ -18,6 +19,16 @@ .permit-expiry-banner { width: 90%; } + + .permit-error-banner { + padding-top: 1.5rem; + + .bc-gov-alertbanner { + background-color: $bc-messages-red-background; + color: $bc-messages-red-text; + margin-bottom: 0; + } + } } } diff --git a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx index ff44a2fdb..bc5620188 100644 --- a/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/ReviewPermitDetails.tsx @@ -1,19 +1,20 @@ import { Box, Typography } from "@mui/material"; import { Dayjs } from "dayjs"; - -import "./ReviewPermitDetails.scss"; +import { ErrorBcGovBanner } from "../../../../../../common/components/banners/ErrorBcGovBanner"; import { PermitExpiryDateBanner } from "../../../../../../common/components/banners/PermitExpiryDateBanner"; -import { ReviewConditionsTable } from "./ReviewConditionsTable"; -import { DiffChip } from "./DiffChip"; -import { Nullable } from "../../../../../../common/types/common"; -import { PermitCondition } from "../../../../types/PermitCondition"; -import { BASE_DAYS_IN_YEAR } from "../../../../constants/constants"; -import { applyWhenNotNullable } from "../../../../../../common/helpers/util"; import { areValuesDifferent } from "../../../../../../common/helpers/equality"; import { DATE_FORMATS, dayjsToLocalStr, } from "../../../../../../common/helpers/formatDate"; +import { applyWhenNotNullable } from "../../../../../../common/helpers/util"; +import { Nullable } from "../../../../../../common/types/common"; +import { BASE_DAYS_IN_YEAR } from "../../../../constants/constants"; +import { PermitCondition } from "../../../../types/PermitCondition"; +import { DiffChip } from "./DiffChip"; +import { ReviewConditionsTable } from "./ReviewConditionsTable"; +import "./ReviewPermitDetails.scss"; +import { pastStartOrExpiryDate } from "../../../../../../common/helpers/validationMessages"; export const ReviewPermitDetails = ({ startDate, @@ -23,6 +24,7 @@ export const ReviewPermitDetails = ({ showChangedFields = false, oldStartDate, oldDuration, + showDateErrorBanner, }: { startDate?: Nullable; permitDuration?: Nullable; @@ -31,6 +33,7 @@ export const ReviewPermitDetails = ({ showChangedFields?: boolean; oldStartDate?: Nullable; oldDuration?: Nullable; + showDateErrorBanner?: Nullable; }) => { const changedFields = showChangedFields ? { @@ -87,7 +90,8 @@ export const ReviewPermitDetails = ({ data-testid="permit-duration" > {applyWhenNotNullable( - (duration) => duration === BASE_DAYS_IN_YEAR ? "1 Year" : `${duration} Days`, + (duration) => + duration === BASE_DAYS_IN_YEAR ? "1 Year" : `${duration} Days`, permitDuration, "", )} @@ -102,6 +106,12 @@ export const ReviewPermitDetails = ({ )} /> + + {showDateErrorBanner && ( + + + + )} Selected commodities and their respective CVSE forms. diff --git a/frontend/src/features/queue/components/ApplicationInQueueReview.tsx b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx index f13a0fe38..2ec33efaa 100644 --- a/frontend/src/features/queue/components/ApplicationInQueueReview.tsx +++ b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx @@ -13,11 +13,11 @@ import { Application } from "../../permits/types/application"; import { PERMIT_REVIEW_CONTEXTS } from "../../permits/types/PermitReviewContext"; import { DEFAULT_PERMIT_TYPE } from "../../permits/types/PermitType"; import { useFetchSpecialAuthorizations } from "../../settings/hooks/specialAuthorizations"; -import { useUpdateApplicationInQueueStatus } from "../hooks/hooks"; import { CASE_ACTIVITY_TYPES } from "../types/CaseActivityType"; import "./ApplicationInQueueReview.scss"; import { QueueBreadcrumb } from "./QueueBreadcrumb"; import { RejectApplicationModal } from "./RejectApplicationModal"; +import { useUpdateApplicationInQueueStatus } from "../hooks/hooks"; export const ApplicationInQueueReview = ({ applicationData, @@ -150,6 +150,14 @@ export const ApplicationInQueueReview = ({ isPending={updateApplicationMutationPending} /> )} + {showRejectApplicationModal && ( + setShowRejectApplicationModal(false)} + onConfirm={handleReject} + isPending={updateApplicationMutationPending} + /> + )}
); }; diff --git a/frontend/src/features/queue/hooks/hooks.ts b/frontend/src/features/queue/hooks/hooks.ts index 8e6ab2701..f80cfa8db 100644 --- a/frontend/src/features/queue/hooks/hooks.ts +++ b/frontend/src/features/queue/hooks/hooks.ts @@ -20,6 +20,8 @@ import { } from "../apiManager/queueAPI"; import { canViewApplicationQueue } from "../helpers/canViewApplicationQueue"; import { CaseActivityType } from "../types/CaseActivityType"; +import { useNavigate } from "react-router-dom"; +import { ERROR_ROUTES } from "../../../routes/constants"; const QUEUE_QUERY_KEYS_BASE = "queue"; @@ -160,6 +162,7 @@ export const useClaimApplicationInQueueMutation = () => { export const useUpdateApplicationInQueueStatus = () => { const { invalidate } = useInvalidateApplicationsInQueue(); + const navigate = useNavigate(); return useMutation({ mutationFn: (data: { @@ -173,7 +176,13 @@ export const useUpdateApplicationInQueueStatus = () => { onSuccess: () => { invalidate(); }, - onError: (err: AxiosError) => err, + onError: (err: AxiosError) => { + if (err.response?.status === 422) { + return err; + } else { + navigate(ERROR_ROUTES.UNEXPECTED); + } + }, }); }; From 55c92adebf6e3849da8cf7e77de7f69a962acde3 Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:37:44 -0800 Subject: [PATCH 51/58] ORV2-2823 - FE: Add rejectionHistory to Application response data type (#1655) Signed-off-by: glen-aot <160973940+glen-aot@users.noreply.github.com> Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../permits/helpers/deserializeApplication.ts | 8 ++- .../components/review/PermitReview.tsx | 14 +++++ .../ReviewApplicationRejectionHistory.scss | 21 ++++++++ .../ReviewApplicationRejectionHistory.tsx | 52 +++++++++++++++++++ .../types/ApplicationRejectionHistory.ts | 6 +++ .../src/features/permits/types/application.ts | 2 + .../components/ApplicationInQueueReview.tsx | 1 + 7 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 frontend/src/features/permits/pages/Application/components/review/ReviewApplicationRejectionHistory.scss create mode 100644 frontend/src/features/permits/pages/Application/components/review/ReviewApplicationRejectionHistory.tsx create mode 100644 frontend/src/features/permits/types/ApplicationRejectionHistory.ts diff --git a/frontend/src/features/permits/helpers/deserializeApplication.ts b/frontend/src/features/permits/helpers/deserializeApplication.ts index 47486ff2c..08f9f9e0d 100644 --- a/frontend/src/features/permits/helpers/deserializeApplication.ts +++ b/frontend/src/features/permits/helpers/deserializeApplication.ts @@ -2,7 +2,13 @@ import { Dayjs } from "dayjs"; import { applyWhenNotNullable } from "../../../common/helpers/util"; import { Application, ApplicationResponseData } from "../types/application"; -import { getEndOfDate, getStartOfDate, now, toLocalDayjs, utcToLocalDayjs } from "../../../common/helpers/formatDate"; +import { + getEndOfDate, + getStartOfDate, + now, + toLocalDayjs, + utcToLocalDayjs, +} from "../../../common/helpers/formatDate"; import { getDurationOrDefault } from "./getDefaultApplicationFormData"; import { getExpiryDate } from "./permitState"; import { minDurationForPermitType } from "./dateSelection"; diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx index 8d705f728..3c7d72ea0 100644 --- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx @@ -22,6 +22,8 @@ import { ReviewFeeSummary } from "./ReviewFeeSummary"; import { ReviewPermitDetails } from "./ReviewPermitDetails"; import { ReviewPermitLOAs } from "./ReviewPermitLOAs"; import { ReviewVehicleInfo } from "./ReviewVehicleInfo"; +import { ApplicationRejectionHistory } from "../../../../types/ApplicationRejectionHistory"; +import { ReviewApplicationRejectionHistory } from "./ReviewApplicationRejectionHistory"; import { isPermitStartOrExpiryDateInPast } from "../../../../helpers/dateSelection"; interface PermitReviewProps { @@ -58,9 +60,15 @@ interface PermitReviewProps { calculatedFee: string; doingBusinessAs?: Nullable; loas?: Nullable; + applicationRejectionHistory?: Nullable; } export const PermitReview = (props: PermitReviewProps) => { + const shouldShowRejectionHistory = + props.reviewContext === PERMIT_REVIEW_CONTEXTS.QUEUE && + props.applicationRejectionHistory && + props.applicationRejectionHistory.length > 0; + const invalidPermitDates = props.permitStartDate && props.permitExpiryDate ? isPermitStartOrExpiryDateInPast( @@ -115,6 +123,12 @@ export const PermitReview = (props: PermitReviewProps) => { oldFields={props.oldFields?.permitData?.vehicleDetails} /> + {shouldShowRejectionHistory && props.applicationRejectionHistory && ( + + )} + { + const { idirUserDetails } = useContext(OnRouteBCContext); + return ( + + + + Rejection History + + + + + {applicationRejectionHistory.map((item) => ( + +
+ + {canViewApplicationQueue(idirUserDetails?.userRole) + ? `${item.userName}, ${toLocal(item.dateTime, DATE_FORMATS.LONG)}` + : toLocal(item.dateTime, DATE_FORMATS.LONG)} + +
+
+ {item.caseNotes} +
+
+ ))} +
+
+
+ ); +}; diff --git a/frontend/src/features/permits/types/ApplicationRejectionHistory.ts b/frontend/src/features/permits/types/ApplicationRejectionHistory.ts new file mode 100644 index 000000000..1551fb95c --- /dev/null +++ b/frontend/src/features/permits/types/ApplicationRejectionHistory.ts @@ -0,0 +1,6 @@ +export interface ApplicationRejectionHistory { + caseActivityId: number; + userName: string; + dateTime: string; + caseNotes: string; +} diff --git a/frontend/src/features/permits/types/application.ts b/frontend/src/features/permits/types/application.ts index fb4a0314c..49973bd47 100644 --- a/frontend/src/features/permits/types/application.ts +++ b/frontend/src/features/permits/types/application.ts @@ -8,6 +8,7 @@ import { PermitApplicationOrigin } from "./PermitApplicationOrigin"; import { PermitApprovalSource } from "./PermitApprovalSource"; import { PermitData } from "./PermitData"; import { ApplicationQueueStatus } from "../../queue/types/ApplicationQueueStatus"; +import { ApplicationRejectionHistory } from "./ApplicationRejectionHistory"; /** * A partial permit type that consists of all common fields used for a permit. @@ -39,6 +40,7 @@ export interface Application extends PartialApplication { updatedDateTime?: Nullable; permitData: PermitData; applicant?: Nullable; + rejectionHistory?: Nullable; } /** diff --git a/frontend/src/features/queue/components/ApplicationInQueueReview.tsx b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx index 2ec33efaa..8ef5e6b0c 100644 --- a/frontend/src/features/queue/components/ApplicationInQueueReview.tsx +++ b/frontend/src/features/queue/components/ApplicationInQueueReview.tsx @@ -140,6 +140,7 @@ export const ApplicationInQueueReview = ({ } doingBusinessAs={doingBusinessAs} calculatedFee={fee} + applicationRejectionHistory={applicationData?.rejectionHistory} /> {showRejectApplicationModal && ( From c2c644b8122b2330990dd774dc2e64740db16374 Mon Sep 17 00:00:00 2001 From: Krishnan Subramanian <84348052+krishnan-aot@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:39:02 -0800 Subject: [PATCH 52/58] ORV2-2968 Non Resident ICBC Permit BE changes (#1657) --- vehicles/src/common/enum/third-party-liability.enum.ts | 7 +++++++ vehicles/src/common/interface/permit.template.interface.ts | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 vehicles/src/common/enum/third-party-liability.enum.ts diff --git a/vehicles/src/common/enum/third-party-liability.enum.ts b/vehicles/src/common/enum/third-party-liability.enum.ts new file mode 100644 index 000000000..66796cfb8 --- /dev/null +++ b/vehicles/src/common/enum/third-party-liability.enum.ts @@ -0,0 +1,7 @@ +/** + * Third Party Liability for Non resident ICBC permits + */ +export enum ThirdPartyLiability { + DANGEROUS_GOODS = 'DANGEROUS_GOODS', + GENERAL_GOODS = 'GENERAL_GOODS', +} diff --git a/vehicles/src/common/interface/permit.template.interface.ts b/vehicles/src/common/interface/permit.template.interface.ts index 80093aed4..1e66955ab 100644 --- a/vehicles/src/common/interface/permit.template.interface.ts +++ b/vehicles/src/common/interface/permit.template.interface.ts @@ -1,3 +1,5 @@ +import { ThirdPartyLiability } from '../enum/third-party-liability.enum'; + // Data used to populate a .docx template export interface PermitTemplateData { permitName: string; @@ -35,6 +37,10 @@ export interface PermitData { applicationNotes?: string; permittedCommodity?: PermittedCommodity; permittedRoute?: PermittedRoute; + /** + * Third Party Liability for Non resident ICBC permits + */ + thirdPartyLiability?: ThirdPartyLiability; } interface VehicleConfiguration { From c57aedcb4339d177997fb417e9031b03b9b913b8 Mon Sep 17 00:00:00 2001 From: zgong-gov <123983557+zgong-gov@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:21:46 -0800 Subject: [PATCH 53/58] ORV2-2991 - Fix application form/review incorrect datetime display (#1659) --- frontend/src/common/helpers/formatDate.ts | 28 +++++++++++-------- frontend/src/common/helpers/tableHelper.ts | 12 +++++--- .../table/PermitSearchResultColumnDef.tsx | 4 +-- .../features/permits/apiManager/permitsAPI.ts | 3 ++ .../components/permit-list/Columns.tsx | 4 +-- .../src/features/permits/helpers/equality.ts | 2 +- .../Amend/components/AmendPermitForm.tsx | 4 +-- .../Application/components/form/LOATable.tsx | 2 +- .../components/ShoppingCartItem.tsx | 4 +-- .../Void/components/VoidPermitHeader.tsx | 2 ++ .../LOA/list/LOAListColumnDef.tsx | 4 +-- 11 files changed, 42 insertions(+), 27 deletions(-) diff --git a/frontend/src/common/helpers/formatDate.ts b/frontend/src/common/helpers/formatDate.ts index 5771c02da..ca9888669 100644 --- a/frontend/src/common/helpers/formatDate.ts +++ b/frontend/src/common/helpers/formatDate.ts @@ -35,22 +35,22 @@ export const now = () => dayjs(); export const nowUtc = () => dayjs.utc(); /** - * Get local datetime string in a specified format for a given DayJS object. - * @param dayjsObj DayJS object that could be in any timezone + * Get local datetime string in a specified format for a given local DayJS object. + * @param localDayjs Local DayJS object * @param formatStr datetime format to display the datetime in (default ISO-8601) * @returns datetime string representing local datetime in the format specified */ -export const dayjsToLocalStr = (dayjsObj: Dayjs, formatStr?: string) => - dayjs(dayjsObj).local().format(formatStr); +export const dayjsToLocalStr = (localDayjs: Dayjs, formatStr?: string) => + dayjs(localDayjs).format(formatStr); /** - * Get UTC datetime string in a specified format for a given DayJS object. - * @param dayjsObj DayJS object that could be in any timezone + * Get UTC datetime string in a specified format for a given local DayJS object. + * @param localDayjs Local DayJS object * @param formatStr datetime format to display the datetime in (default ISO-8601) * @returns datetime string representing UTC datetime in the format specified */ -export const dayjsToUtcStr = (dayjsObj: Dayjs, formatStr?: string) => - dayjs(dayjsObj).utc().format(formatStr); +export const dayjsToUtcStr = (localDayjs: Dayjs, formatStr?: string) => + dayjs(localDayjs).utc().format(formatStr); /** * Get UTC datetime string in a specified format for a given datetime string @@ -65,10 +65,16 @@ export const toUtc = (dateTimeStr: string, formatStr?: string) => * Get local datetime string in a specified format for a given datetime string * @param dateTimeStr datetime string that could be in any timezone * @param formatStr datetime format to display in (default ISO-8601) + * @param isDateTimeStrLocal Whether or not the provided datetime string is already local * @returns datetime string representing local datetime in the format specified */ -export const toLocal = (dateTimeStr: string, formatStr?: string) => - dayjs(dateTimeStr).local().format(formatStr); +export const toLocal = ( + dateTimeStr: string, + formatStr?: string, + isDateTimeStrLocal?: boolean, +) => isDateTimeStrLocal + ? dayjs(dateTimeStr).format(formatStr) + : dayjs(dateTimeStr).local().format(formatStr); /** * Get local DayJS object for a given UTC datetime string @@ -106,7 +112,7 @@ export const toTimeZone = ( ) => ianaId ? dayjs(datetimeStr).tz(ianaId).format(formatStr) - : toLocal(datetimeStr, formatStr); + : toLocal(datetimeStr, formatStr, true); /** * Gets the number of days between two datetimes (should both be in the same timezone). diff --git a/frontend/src/common/helpers/tableHelper.ts b/frontend/src/common/helpers/tableHelper.ts index 3d131fcd6..342c7350e 100644 --- a/frontend/src/common/helpers/tableHelper.ts +++ b/frontend/src/common/helpers/tableHelper.ts @@ -7,13 +7,17 @@ import { PermitListItem } from "../../features/permits/types/permit"; import { Nullable } from "../types/common"; /** - * Format a given datetime string to a format that we can display - * @param rawDateTime + * Format a datetime string in a table cell to a given display format. + * @param rawDateTime Provided datetime string, if any + * @param isDateTimeLocal Whether or not the provided datetime is local * @returns datetime string for display or "NA" if invalid date given */ -export const formatCellValuetoDatetime = (rawDateTime: Nullable) => { +export const formatCellValuetoDatetime = ( + rawDateTime: Nullable, + isDateTimeLocal?: boolean, +) => { return applyWhenNotNullable( - (dt) => toLocal(dt, DATE_FORMATS.DATEONLY_ABBR_MONTH), + (dt) => toLocal(dt, DATE_FORMATS.DATEONLY_ABBR_MONTH, isDateTimeLocal), rawDateTime, "NA", ); diff --git a/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx b/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx index 8d956e1ca..253ee9a5f 100644 --- a/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx +++ b/frontend/src/features/idir/search/table/PermitSearchResultColumnDef.tsx @@ -105,7 +105,7 @@ export const PermitSearchResultColumnDef = ( enableSorting: true, sortingFn: dateTimeStringSortingFn, Cell: (props: { cell: any }) => { - const formattedDate = formatCellValuetoDatetime(props.cell.getValue()); + const formattedDate = formatCellValuetoDatetime(props.cell.getValue(), true); return formattedDate; }, }, @@ -115,7 +115,7 @@ export const PermitSearchResultColumnDef = ( enableSorting: true, sortingFn: dateTimeStringSortingFn, Cell: (props: { cell: any }) => { - const formattedDate = formatCellValuetoDatetime(props.cell.getValue()); + const formattedDate = formatCellValuetoDatetime(props.cell.getValue(), true); return formattedDate; }, }, diff --git a/frontend/src/features/permits/apiManager/permitsAPI.ts b/frontend/src/features/permits/apiManager/permitsAPI.ts index 52ac62117..3131adfea 100644 --- a/frontend/src/features/permits/apiManager/permitsAPI.ts +++ b/frontend/src/features/permits/apiManager/permitsAPI.ts @@ -196,6 +196,7 @@ export const getApplications = async ( startDate: toLocal( application?.startDate, DATE_FORMATS.DATEONLY_SHORT_NAME, + true, ), } as ApplicationListItem; }); @@ -494,10 +495,12 @@ export const getPermits = async ( startDate: toLocal( permit.startDate, DATE_FORMATS.DATEONLY_SHORT_NAME, + true, ), expiryDate: toLocal( permit.expiryDate, DATE_FORMATS.DATEONLY_SHORT_NAME, + true, ), } as PermitListItem; }, diff --git a/frontend/src/features/permits/components/permit-list/Columns.tsx b/frontend/src/features/permits/components/permit-list/Columns.tsx index c27bd3ee6..177cb6699 100644 --- a/frontend/src/features/permits/components/permit-list/Columns.tsx +++ b/frontend/src/features/permits/components/permit-list/Columns.tsx @@ -71,7 +71,7 @@ export const PermitsColumnDefinition = ( header: "Permit Start Date", enableSorting: true, Cell: (props: { cell: any }) => { - const formattedDate = formatCellValuetoDatetime(props.cell.getValue()); + const formattedDate = formatCellValuetoDatetime(props.cell.getValue(), true); return formattedDate; }, }, @@ -81,7 +81,7 @@ export const PermitsColumnDefinition = ( id: "expiryDate", enableSorting: true, Cell: (props: { cell: any }) => { - const formattedDate = formatCellValuetoDatetime(props.cell.getValue()); + const formattedDate = formatCellValuetoDatetime(props.cell.getValue(), true); return formattedDate; }, }, diff --git a/frontend/src/features/permits/helpers/equality.ts b/frontend/src/features/permits/helpers/equality.ts index e1d2ddb0f..1987ec74a 100644 --- a/frontend/src/features/permits/helpers/equality.ts +++ b/frontend/src/features/permits/helpers/equality.ts @@ -6,11 +6,11 @@ import { PermitVehicleDetails } from "../types/PermitVehicleDetails"; import { PermitData } from "../types/PermitData"; import { PermitCondition } from "../types/PermitCondition"; import { arePermitLOADetailsEqual, PermitLOA } from "../types/PermitLOA"; +import { doUniqueArraysHaveSameObjects } from "../../../common/helpers/equality"; import { DATE_FORMATS, dayjsToLocalStr, } from "../../../common/helpers/formatDate"; -import { doUniqueArraysHaveSameObjects } from "../../../common/helpers/equality"; /** * Compare whether or not two mailing addresses are equal. diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx index 5e4a44f4b..326c1e1d0 100644 --- a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx +++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx @@ -24,7 +24,7 @@ import { useFetchSpecialAuthorizations } from "../../../../settings/hooks/specia import { filterLOAsForPermitType, filterNonExpiredLOAs } from "../../../helpers/permitLOA"; import { dayjsToUtcStr, - nowUtc, + now, } from "../../../../../common/helpers/formatDate"; import { @@ -213,7 +213,7 @@ export const AmendPermitForm = () => { comment: getDefaultRequiredVal("", history.comment), name: history.commentUsername, revisionDateTime: getDefaultRequiredVal( - dayjsToUtcStr(nowUtc()), + dayjsToUtcStr(now()), history.transactionSubmitDate, ), })); diff --git a/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx b/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx index ce6d0dd6d..93aa7371e 100644 --- a/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx +++ b/frontend/src/features/permits/pages/Application/components/form/LOATable.tsx @@ -85,7 +85,7 @@ export const LOATable = ({ scope="row" > {applyWhenNotNullable( - expiryDate => toLocal(expiryDate, DATE_FORMATS.DATEONLY_SLASH), + expiryDate => toLocal(expiryDate, DATE_FORMATS.DATEONLY_SLASH, true), selectableLOA.loa.expiryDate, "Never expires", )} diff --git a/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx b/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx index 1689e3535..47238ff57 100644 --- a/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx +++ b/frontend/src/features/permits/pages/ShoppingCart/components/ShoppingCartItem.tsx @@ -83,7 +83,7 @@ export const ShoppingCartItem = ({ - {toLocal(cartItemData.startDate, DATE_FORMATS.DATEONLY_ABBR_MONTH)} + {toLocal(cartItemData.startDate, DATE_FORMATS.DATEONLY_ABBR_MONTH, true)}
@@ -113,7 +113,7 @@ export const ShoppingCartItem = ({ - {toLocal(cartItemData.expiryDate, DATE_FORMATS.DATEONLY_ABBR_MONTH)} + {toLocal(cartItemData.expiryDate, DATE_FORMATS.DATEONLY_ABBR_MONTH, true)}
diff --git a/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx b/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx index af5f3e07e..bb338612d 100644 --- a/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx +++ b/frontend/src/features/permits/pages/Void/components/VoidPermitHeader.tsx @@ -47,6 +47,7 @@ export const VoidPermitHeader = ({ permit }: { permit: Nullable }) => { {toLocal( permit.permitData.startDate, DATE_FORMATS.DATEONLY_ABBR_MONTH, + true, )}
@@ -62,6 +63,7 @@ export const VoidPermitHeader = ({ permit }: { permit: Nullable }) => { {toLocal( permit.permitData.expiryDate, DATE_FORMATS.DATEONLY_ABBR_MONTH, + true, )} diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx index ea09f28e4..a8d3e8e7e 100644 --- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx +++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx @@ -36,7 +36,7 @@ export const LOAListColumnDef = ( }, { accessorFn: (originalRow) => { - return toLocal(originalRow.startDate, DATE_FORMATS.DATEONLY_SLASH); + return toLocal(originalRow.startDate, DATE_FORMATS.DATEONLY_SLASH, true); }, id: "startDate", header: "Start Date", @@ -54,7 +54,7 @@ export const LOAListColumnDef = ( { accessorFn: (originalRow) => applyWhenNotNullable( - (expiryDate) => toLocal(expiryDate, DATE_FORMATS.DATEONLY_SLASH), + (expiryDate) => toLocal(expiryDate, DATE_FORMATS.DATEONLY_SLASH, true), originalRow.expiryDate, "Never expires", ) as string, From 2a81a2587bf6f10b1c5803af9e2374c2acd878fa Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:16:28 -0500 Subject: [PATCH 54/58] fix:ORV2-2977 - Incorrect PermitFee amount in Permit Document and formatting (#1661) --- vehicles/src/common/constants/api.constant.ts | 3 +-- .../src/common/helper/format-template-data.helper.ts | 10 +++++++--- .../src/common/helper/permit-application.helper.ts | 9 ++++++--- vehicles/src/common/helper/permit-fee.helper.ts | 4 ++-- .../application/application.service.ts | 4 ++-- .../payment/payment.service.ts | 11 ++++++++--- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/vehicles/src/common/constants/api.constant.ts b/vehicles/src/common/constants/api.constant.ts index b15b80cbe..ffcbeb052 100644 --- a/vehicles/src/common/constants/api.constant.ts +++ b/vehicles/src/common/constants/api.constant.ts @@ -8,6 +8,5 @@ export const CRYPTO_ALGORITHM_MD5 = 'md5'; export const CRYPTO_ALGORITHM_SHA256 = 'sha256'; export const TOKEN_EXPIRY_BUFFER = 15; export const PERMISSIONS_KEY = 'permissions'; -export const TIMEZONE_PACIFIC = "America/Vancouver"; +export const TIMEZONE_PACIFIC = 'America/Vancouver'; export const GL_PROJ_CODE_PLACEHOLDER = 'PROJECT'; - diff --git a/vehicles/src/common/helper/format-template-data.helper.ts b/vehicles/src/common/helper/format-template-data.helper.ts index a82101252..8181ef3d8 100644 --- a/vehicles/src/common/helper/format-template-data.helper.ts +++ b/vehicles/src/common/helper/format-template-data.helper.ts @@ -9,6 +9,7 @@ import { PermitTemplateData, } from '../interface/permit.template.interface'; import { FullNamesForDgen } from '../interface/full-names-for-dgen.interface'; +import { formatAmount } from './payment.helper'; /** * Formats the permit data so that it can be used in the templated word documents @@ -87,9 +88,12 @@ export const formatTemplateData = ( template.companyAlternateName = companyInfo.alternateName; // Format Fee Summary - template.permitData.feeSummary = permit.permitTransactions - ?.at(0) - ?.transactionAmount.toString(); + const transcation = permit.permitTransactions?.at(0)?.transaction; + + template.permitData.feeSummary = formatAmount( + transcation.transactionTypeId, + permit.permitTransactions?.at(0)?.transactionAmount, + ).toString(); revisionHistory?.forEach((revision) => { if ( diff --git a/vehicles/src/common/helper/permit-application.helper.ts b/vehicles/src/common/helper/permit-application.helper.ts index b572f57e9..96d3b53e4 100644 --- a/vehicles/src/common/helper/permit-application.helper.ts +++ b/vehicles/src/common/helper/permit-application.helper.ts @@ -268,9 +268,12 @@ export const isPermitTypeEligibleForQueue = ( return PERMIT_TYPES_FOR_QUEUE.includes(permitType); }; -export const validApplicationDates = (application: Permit, timezone: string): boolean => { +export const validApplicationDates = ( + application: Permit, + timezone: string, +): boolean => { const todayUTC = dayjs(new Date()); - const todayPacific = todayUTC.tz(timezone).format("YYYY-MM-DD"); + const todayPacific = todayUTC.tz(timezone).format('YYYY-MM-DD'); const { startDate, expiryDate } = application.permitData; return startDate >= todayPacific && startDate <= expiryDate; -} +}; diff --git a/vehicles/src/common/helper/permit-fee.helper.ts b/vehicles/src/common/helper/permit-fee.helper.ts index 8e2314c02..dc14bf979 100644 --- a/vehicles/src/common/helper/permit-fee.helper.ts +++ b/vehicles/src/common/helper/permit-fee.helper.ts @@ -205,7 +205,7 @@ export const validAmount = ( calculatedAmount: number, receivedAmount: number, transactionType: TransactionType, -): boolean =>{ +): boolean => { const isAmountValid = receivedAmount.toFixed(2) === Math.abs(calculatedAmount).toFixed(2); @@ -218,4 +218,4 @@ export const validAmount = ( isAmountValid && (isRefundValid || transactionType !== TransactionType.REFUND) ); -} +}; diff --git a/vehicles/src/modules/permit-application-payment/application/application.service.ts b/vehicles/src/modules/permit-application-payment/application/application.service.ts index 81f29aef6..5cde7daa3 100644 --- a/vehicles/src/modules/permit-application-payment/application/application.service.ts +++ b/vehicles/src/modules/permit-application-payment/application/application.service.ts @@ -1236,8 +1236,8 @@ export class ApplicationService { company: { companyId: permit.company.companyId }, }, }); - if(loaDetails.length != loaIdsToInsert.length) - throw new BadRequestException('One or more loa(s) does not exist') + if (loaDetails.length != loaIdsToInsert.length) + throw new BadRequestException('One or more loa(s) does not exist'); // Transform the permit LOA IDs from an array of numbers into individual records. const singlePermitLoa = loaIdsToInsert.map((loaId) => ({ permitId, diff --git a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts index e4a061f1a..e511f1ced 100644 --- a/vehicles/src/modules/permit-application-payment/payment/payment.service.ts +++ b/vehicles/src/modules/permit-application-payment/payment/payment.service.ts @@ -51,7 +51,10 @@ import { } from 'src/common/helper/permit-fee.helper'; import { CfsTransactionDetail } from './entities/cfs-transaction.entity'; import { CfsFileStatus } from 'src/common/enum/cfs-file-status.enum'; -import { isAmendmentApplication, validApplicationDates } from '../../../common/helper/permit-application.helper'; +import { + isAmendmentApplication, + validApplicationDates, +} from '../../../common/helper/permit-application.helper'; import { isCfsPaymentMethodType } from 'src/common/helper/payment.helper'; import { PgApprovesStatus } from 'src/common/enum/pg-approved-status-type.enum'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; @@ -513,8 +516,10 @@ export class PaymentService { // Calculate and add amount for each requested application, as per the available backend data. for (const application of applications) { //Check if each application has a valid start date and valid expiry date. - if (isCVClientUser && !validApplicationDates(application, TIMEZONE_PACIFIC)) - { + if ( + isCVClientUser && + !validApplicationDates(application, TIMEZONE_PACIFIC) + ) { throw new UnprocessableEntityException( `Atleast one of the application has invalid startDate or expiryDate.`, ); From b6a7322af03fff1f8aedf13b694be619757f4f69 Mon Sep 17 00:00:00 2001 From: Praveen Raju <80779423+praju-aot@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:40:57 -0500 Subject: [PATCH 55/58] feat:ORV2-2950 - Update MFP name to Motive Fuel User Permit (#1662) Co-authored-by: John Fletcher --- .../versions/revert/v_47_ddl_revert.sql | 38 +++++++++++++++++ database/mssql/scripts/versions/v_47_ddl.sql | 42 +++++++++++++++++++ database/mssql/test/versions/v_47_1_test.sql | 5 +++ database/mssql/test/versions/v_47_test.sh | 16 +++++++ 4 files changed, 101 insertions(+) create mode 100644 database/mssql/scripts/versions/revert/v_47_ddl_revert.sql create mode 100644 database/mssql/scripts/versions/v_47_ddl.sql create mode 100644 database/mssql/test/versions/v_47_1_test.sql create mode 100644 database/mssql/test/versions/v_47_test.sh diff --git a/database/mssql/scripts/versions/revert/v_47_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_47_ddl_revert.sql new file mode 100644 index 000000000..da54853eb --- /dev/null +++ b/database/mssql/scripts/versions/revert/v_47_ddl_revert.sql @@ -0,0 +1,38 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + +UPDATE [permit].[ORBC_PERMIT_TYPE] SET NAME='Motive Fuel User',[DB_LAST_UPDATE_TIMESTAMP] =getutcdate() WHERE PERMIT_TYPE='MFP'; + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Reverting permit name update for MFP' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (46, @VersionDescription, getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO diff --git a/database/mssql/scripts/versions/v_47_ddl.sql b/database/mssql/scripts/versions/v_47_ddl.sql new file mode 100644 index 000000000..d1c0a7334 --- /dev/null +++ b/database/mssql/scripts/versions/v_47_ddl.sql @@ -0,0 +1,42 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO + +UPDATE [permit].[ORBC_PERMIT_TYPE] SET NAME='Motive Fuel User Permit',[DB_LAST_UPDATE_TIMESTAMP] =getutcdate() WHERE PERMIT_TYPE='MFP'; + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Update permit name of permit type MFP' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (47, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO + diff --git a/database/mssql/test/versions/v_47_1_test.sql b/database/mssql/test/versions/v_47_1_test.sql new file mode 100644 index 000000000..a6976c848 --- /dev/null +++ b/database/mssql/test/versions/v_47_1_test.sql @@ -0,0 +1,5 @@ +-- Test that the permit name has been configured correctly +SET NOCOUNT ON + +SELECT COUNT(*) FROM $(DB_NAME).[permit].[ORBC_PERMIT_TYPE] +WHERE NAME='Motive Fuel User Permit' \ No newline at end of file diff --git a/database/mssql/test/versions/v_47_test.sh b/database/mssql/test/versions/v_47_test.sh new file mode 100644 index 000000000..a0be927ff --- /dev/null +++ b/database/mssql/test/versions/v_47_test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Retrieve arguments +source ${SCRIPT_DIR}/utility/getopt.sh +USAGE="-u USER -p PASS -s SERVER -d DATABASE" +parse_options "${USAGE}" ${@} + +# All database tests for database version 47 are run from this shell script. +# TESTS_DIR variable set by the calling test-runner script. + +TEST_47_1_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_47_1_test.sql | xargs) +if [[ $TEST_47_1_RESULT -eq 1 ]]; then + echo "Test 47.1 passed: MFP Permit name update successful." +else + echo "******** Test 47.1 failed: MFP Permit name update failed." +fi From 553f43766e3acf21250ac5a9d9b16469f951317d Mon Sep 17 00:00:00 2001 From: Krishnan Subramanian <84348052+krishnan-aot@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:39:00 -0800 Subject: [PATCH 56/58] ORV2-2968 Adding ICBC Permit types in Database (#1658) Co-authored-by: John Fletcher --- .../versions/revert/v_48_ddl_revert.sql | 53 +++++++++++++++++++ database/mssql/scripts/versions/v_48_ddl.sql | 40 ++++++++++++++ database/mssql/test/versions/v_48_1_test.sql | 5 ++ database/mssql/test/versions/v_48_test.sh | 16 ++++++ vehicles/src/common/enum/permit-type.enum.ts | 2 + 5 files changed, 116 insertions(+) create mode 100644 database/mssql/scripts/versions/revert/v_48_ddl_revert.sql create mode 100644 database/mssql/scripts/versions/v_48_ddl.sql create mode 100644 database/mssql/test/versions/v_48_1_test.sql create mode 100644 database/mssql/test/versions/v_48_test.sh diff --git a/database/mssql/scripts/versions/revert/v_48_ddl_revert.sql b/database/mssql/scripts/versions/revert/v_48_ddl_revert.sql new file mode 100644 index 000000000..48ab33189 --- /dev/null +++ b/database/mssql/scripts/versions/revert/v_48_ddl_revert.sql @@ -0,0 +1,53 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +BEGIN TRANSACTION +GO + +/* +The following DELETE statement is intentionally commented out. +This is because, these permit types are a permanent part of the production db +and there is no expectation that they will be deleted at any point. + +If at all needed, the following query can be executed manually in prod at the team's discretion. + +IMPORTANT: Analyze the impact from foreign key constraints in child tables +before deleting the permit types. + +*/ + +-- DELETE FROM [permit].[ORBC_PERMIT_TYPE] WHERE PERMIT_TYPE IN ('QRFR', 'STFR') +-- GO + + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Reverting adding QRFR and STFR permit types to ORBC_PERMIT_TYPE table' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [RELEASE_DATE]) VALUES (47, @VersionDescription, getutcdate()) + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database revert succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database revert failed' +END +GO \ No newline at end of file diff --git a/database/mssql/scripts/versions/v_48_ddl.sql b/database/mssql/scripts/versions/v_48_ddl.sql new file mode 100644 index 000000000..69b5b811e --- /dev/null +++ b/database/mssql/scripts/versions/v_48_ddl.sql @@ -0,0 +1,40 @@ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET NOCOUNT ON +GO + +SET XACT_ABORT ON +GO +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE +GO +BEGIN TRANSACTION +GO + +INSERT [permit].[ORBC_PERMIT_TYPE] ([PERMIT_TYPE], [NAME]) VALUES (N'QRFR', N'Non-Resident Quarterly ICBC Basic Insurance (FR)') +INSERT [permit].[ORBC_PERMIT_TYPE] ([PERMIT_TYPE], [NAME]) VALUES (N'STFR', N'Non-Resident Single Trip ICBC Basic Insurance (FR)') + +IF @@ERROR <> 0 SET NOEXEC ON +GO + +DECLARE @VersionDescription VARCHAR(255) +SET @VersionDescription = 'Add QRFR and STFR permit types to ORBC_PERMIT_TYPE table' + +INSERT [dbo].[ORBC_SYS_VERSION] ([VERSION_ID], [DESCRIPTION], [UPDATE_SCRIPT], [REVERT_SCRIPT], [RELEASE_DATE]) VALUES (48, @VersionDescription, '$(UPDATE_SCRIPT)', '$(REVERT_SCRIPT)', getutcdate()) +IF @@ERROR <> 0 SET NOEXEC ON +GO + +COMMIT TRANSACTION +GO +IF @@ERROR <> 0 SET NOEXEC ON +GO +DECLARE @Success AS BIT +SET @Success = 1 +SET NOEXEC OFF +IF (@Success = 1) PRINT 'The database update succeeded' +ELSE BEGIN + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION + PRINT 'The database update failed' +END +GO diff --git a/database/mssql/test/versions/v_48_1_test.sql b/database/mssql/test/versions/v_48_1_test.sql new file mode 100644 index 000000000..1a7a46339 --- /dev/null +++ b/database/mssql/test/versions/v_48_1_test.sql @@ -0,0 +1,5 @@ +-- Test that the auth groups have been inserted correctly +SET NOCOUNT ON + +SELECT COUNT(*) FROM $(DB_NAME).[permit].[ORBC_PERMIT_TYPE] +WHERE PERMIT_TYPE IN ('STFR', 'QRFR') diff --git a/database/mssql/test/versions/v_48_test.sh b/database/mssql/test/versions/v_48_test.sh new file mode 100644 index 000000000..d9a61389d --- /dev/null +++ b/database/mssql/test/versions/v_48_test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Retrieve arguments +source ${SCRIPT_DIR}/utility/getopt.sh +USAGE="-u USER -p PASS -s SERVER -d DATABASE" +parse_options "${USAGE}" ${@} + +# All database tests for database version 48 are run from this shell script. +# TESTS_DIR variable set by the calling test-runner script. + +TEST_48_1_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_48_1_test.sql | xargs) +if [[ $TEST_48_1_RESULT -eq 2 ]]; then + echo "Test 48.1 passed: QRFR and STFR permit types exist." +else + echo "******** Test 48.1 failed: QRFR and STFR permit types do not exist." +fi diff --git a/vehicles/src/common/enum/permit-type.enum.ts b/vehicles/src/common/enum/permit-type.enum.ts index b332c90fb..4fbd1de85 100644 --- a/vehicles/src/common/enum/permit-type.enum.ts +++ b/vehicles/src/common/enum/permit-type.enum.ts @@ -22,6 +22,8 @@ export enum PermitType { SINGLE_TRIP_OVERWEIGHT_OVERSIZE = 'STWS', TERM_AXLE_OVERWEIGHT = 'TRAX', TERM_OVERWEIGHT = 'TROW', + NON_RESIDENT_QUARTERLY_ICBC_BASIC_INSURANCE_FR = 'QRFR', + NON_RESIDENT_SINGLE_TRIP_ICBC_BASIC_INSURANCE_FR = 'STFR', } export enum ExtendedPermitType { From 9d5d355210b13a029da325a668b7cd3b391c220c Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:15:37 -0800 Subject: [PATCH 57/58] ORV2-3020 - FE: CV client should see rejection history (#1664) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../permits/pages/Application/ApplicationReview.tsx | 7 +++---- .../pages/Application/components/review/PermitReview.tsx | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/permits/pages/Application/ApplicationReview.tsx b/frontend/src/features/permits/pages/Application/ApplicationReview.tsx index b362075c1..1c7f78249 100644 --- a/frontend/src/features/permits/pages/Application/ApplicationReview.tsx +++ b/frontend/src/features/permits/pages/Application/ApplicationReview.tsx @@ -27,10 +27,8 @@ import { } from "../../../../routes/constants"; export const ApplicationReview = () => { - const { - applicationData, - setApplicationData: setApplicationContextData, - } = useContext(ApplicationContext); + const { applicationData, setApplicationData: setApplicationContextData } = + useContext(ApplicationContext); const companyId = getDefaultRequiredVal(0, applicationData?.companyId); @@ -171,6 +169,7 @@ export const ApplicationReview = () => { doingBusinessAs={doingBusinessAs} calculatedFee={fee} loas={applicationData?.permitData?.loas} + applicationRejectionHistory={applicationData?.rejectionHistory} />
diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx index 3c7d72ea0..5b1c3934a 100644 --- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx +++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx @@ -65,7 +65,8 @@ interface PermitReviewProps { export const PermitReview = (props: PermitReviewProps) => { const shouldShowRejectionHistory = - props.reviewContext === PERMIT_REVIEW_CONTEXTS.QUEUE && + (props.reviewContext === PERMIT_REVIEW_CONTEXTS.QUEUE || + props.reviewContext === PERMIT_REVIEW_CONTEXTS.APPLY) && props.applicationRejectionHistory && props.applicationRejectionHistory.length > 0; From bc5fd5cb838b42d89a6b23a31f5b3990fd015dff Mon Sep 17 00:00:00 2001 From: glen-aot <160973940+glen-aot@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:14:59 -0800 Subject: [PATCH 58/58] ORV2-2994 - Regression: phone number extensions should all be numeric-only (#1666) Co-authored-by: GlenAOT <160973940+GlenAOT@users.noreply.github.com> --- .../src/features/permits/components/form/ContactDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/permits/components/form/ContactDetails.tsx b/frontend/src/features/permits/components/form/ContactDetails.tsx index e7a9fe83d..f8a3259e2 100644 --- a/frontend/src/features/permits/components/form/ContactDetails.tsx +++ b/frontend/src/features/permits/components/form/ContactDetails.tsx @@ -108,7 +108,7 @@ export const ContactDetails = ({ feature }: { feature: string }) => { />