Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LoA validation on pay now #1718

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions vehicles/src/common/helper/validate-loa.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ReadLoaDto } from 'src/modules/special-auth/dto/response/read-loa.dto';
import { PermitType } from '../enum/permit-type.enum';
import { Loas } from '../interface/permit.template.interface';
import { Permit } from 'src/modules/permit-application-payment/permit/entities/permit.entity';
import * as dayjs from 'dayjs';

export const isVehicleTypeValid = (
permitVehicleType: string,
permitVehicleId: string,
powerUnits?: string[],
trailers?: string[],
): boolean => {
const isPowerUnitAllowed =
permitVehicleType === 'powerUnit'
? powerUnits.includes(permitVehicleId)
: true;

const isTrailerAllowed =
permitVehicleType === 'trailer' ? trailers.includes(permitVehicleId) : true;

return isPowerUnitAllowed && isTrailerAllowed;
};

export const isPermitTypeValid = (
permitTypePermit: PermitType,
permitType: PermitType[],
): boolean => {
return permitType.includes(permitTypePermit);
};

export const isValidDateForLoa = (
loaDetail: Loas | ReadLoaDto,
permit: Permit,
): boolean => {
const { startDate, expiryDate } = loaDetail;
const { startDate: permitStartDate, expiryDate: permitExpiryDate } =
permit.permitData;
return (
dayjs(startDate).isBefore(permitStartDate, 'day') &&
(expiryDate ? dayjs(expiryDate).isAfter(permitExpiryDate, 'day') : true)
);
};
12 changes: 10 additions & 2 deletions vehicles/src/common/interface/permit.template.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PermitType } from '../enum/permit-type.enum';
import { ThirdPartyLiability } from '../enum/third-party-liability.enum';

// Data used to populate a .docx template
Expand Down Expand Up @@ -92,6 +93,7 @@ interface ContactDetails {
}

interface VehicleDetails {
vehicleId: string;
vin: string;
plate: string;
make: string;
Expand All @@ -112,8 +114,14 @@ interface Commodities {
disabled?: boolean;
}

interface Loas {
loaId: string;
export interface Loas {
loaId: number;
loaNumber: number;
checked: boolean;
loaPermitType: PermitType[];
startDate: string;
expiryDate?: string;
powerUnits?: string[];
trailers?: string[];
disabled?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PaymentMethodType } from './entities/payment-method-type.entity';
import { PaymentReportService } from './payment-report.service';
import { Permit } from '../permit/entities/permit.entity';
import { CfsTransactionDetail } from './entities/cfs-transaction.entity';
import { LoaDetail } from 'src/modules/special-auth/entities/loa-detail.entity';

@Module({
imports: [
Expand All @@ -22,6 +23,7 @@ import { CfsTransactionDetail } from './entities/cfs-transaction.entity';
PaymentCardType,
PaymentMethodType,
CfsTransactionDetail,
LoaDetail,
]),
],
controllers: [PaymentController],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ import {
} 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';
import { PermitData } from 'src/common/interface/permit.template.interface';
import { LoaDetail } from 'src/modules/special-auth/entities/loa-detail.entity';
import {
isPermitTypeValid,
isValidDateForLoa,
isVehicleTypeValid,
} from 'src/common/helper/validate-loa.helper';
import { ReadLoaDto } from 'src/modules/special-auth/dto/response/read-loa.dto';

@Injectable()
export class PaymentService {
Expand All @@ -87,6 +95,8 @@ export class PaymentService {
private paymentMethodTypeRepository: Repository<PaymentMethodType>,
@InjectRepository(PaymentCardType)
private paymentCardTypeRepository: Repository<PaymentCardType>,
@InjectRepository(LoaDetail)
private loaDetailRepository: Repository<LoaDetail>,
@InjectMapper() private readonly classMapper: Mapper,
@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache,
Expand Down Expand Up @@ -331,6 +341,13 @@ export class PaymentService {
throw new BadRequestException(
'Application in its current status cannot be processed for payment.',
);
const permitData = JSON.parse(
application.permitData.permitData,
) as PermitData;
// If application includes LoAs then validate Loa data.
if (permitData.loas) {
await this.isValidLoa(application);
}
}
const totalTransactionAmount = await this.validateApplicationAndPayment(
createTransactionDto,
Expand Down Expand Up @@ -887,4 +904,137 @@ export class PaymentService {
})),
) as PermitHistoryDto[];
}

async isValidLoa(permit: Permit): Promise<void> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be part of the validate endpoint?

const { companyId } = permit.company;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add optional chaining

const permitData = JSON.parse(permit.permitData.permitData) as PermitData;
const { vehicleId: permitVehicleId, vehicleType: permitVehicleType } =
permitData.vehicleDetails;
const loaNumbers = permitData.loas.map((loa) => loa.loaNumber);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add optional chaining

const readLoaDto = await this.findLoas(companyId, loaNumbers);

// Validate LOA details and permit data against database entries
this.validateLoaDetails(
readLoaDto,
permit,
permitVehicleId,
permitVehicleType,
);

// validate LoA snapshot in permit Data
this.validatePermitDataAgainstLoas(
permitData,
permit,
permitVehicleId,
permitVehicleType,
);
}
private validateLoaDetails(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor to Loa helper or util or the loa module

readLoaDtos: ReadLoaDto[],
permit: Permit,
permitVehicleId: string,
permitVehicleType: string,
) {
for (const readLoaDto of readLoaDtos) {
const loaPowerUnits = readLoaDto.powerUnits;
const loaTrailers = readLoaDto.trailers;
const loaPermitTypes = readLoaDto.loaPermitType;
if (!isValidDateForLoa(readLoaDto, permit)) {
throw new UnprocessableEntityException(
`${permit.applicationNumber} has LoA with invalid date(s).`,
);
}
if (
!isVehicleTypeValid(
permitVehicleType,
permitVehicleId,
loaPowerUnits,
loaTrailers,
)
) {
throw new UnprocessableEntityException(
`${permit.applicationNumber} has LoA with invalid vehicle(s).`,
);
}
if (!isPermitTypeValid(permit.permitType, loaPermitTypes)) {
throw new UnprocessableEntityException(
`${permit.applicationNumber} ha LoA with invalid permitType.`,
);
}
}
}

private validatePermitDataAgainstLoas(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor to Loa helper or util or the loa module

permitData: PermitData,
permit: Permit,
permitVehicleId: string,
permitVehicleType: string,
) {
for (const loa of permitData.loas) {
const permitLoaPowerUnits = loa.powerUnits;
const permitLoaTrailers = loa.trailers;
const permitTypesLoa = loa.loaPermitType;
if (!isValidDateForLoa(loa, permit)) {
throw new UnprocessableEntityException(
`${permit.applicationNumber} has LoA snapshot with invalid date(s).`,
);
}

if (
!isVehicleTypeValid(
permitVehicleType,
permitVehicleId,
permitLoaPowerUnits,
permitLoaTrailers,
)
) {
throw new UnprocessableEntityException(
`${permit.applicationNumber} has LoA snapshot with invalid vehicle(s).`,
);
}
if (!isPermitTypeValid(permit.permitType, permitTypesLoa)) {
throw new UnprocessableEntityException(
`${permit.applicationNumber} has LoA snapshot with invalid permitType.`,
);
}
}
}

/**
* Retrieves a single LOA (Letter of Authorization) detail for a specified company.
*
* Steps:
* 1. Fetches the LOA detail from the repository based on company ID and LOA Number.
* 2. Ensures the fetched LOA detail is active.
* 3. Includes relations (company, loaVehicles, loaPermitTypes) in the query.
*
* @param {number} companyId - ID of the company for which to fetch the LOA detail.
* @param {number} loaId - ID of the LOA to be fetched.
* @returns {Promise<LoaDetail>} - Returns a Promise that resolves to the LOA detail.
*/
@LogAsyncMethodExecution()
async findLoas(
companyId: number,
loaNumbers: number[],
): Promise<ReadLoaDto[]> {
// Fetch initial active LOA details
const loaDetails = await this.loaDetailRepository.find({
where: {
loaNumber: In(loaNumbers),
isActive: true,
company: { companyId },
},
relations: ['company', 'loaVehicles', 'loaPermitTypes'],
});

const readLoaDto = await this.classMapper.mapArrayAsync(
loaDetails,
LoaDetail,
ReadLoaDto,
{
extraArgs: () => ({ companyId: companyId }),
},
);
return readLoaDto;
}
}
Loading