Skip to content

Commit

Permalink
ORV2-2792 Staff Queue Changes (#1603)
Browse files Browse the repository at this point in the history
  • Loading branch information
praju-aot authored Sep 18, 2024
1 parent fdc89b1 commit 84c5625
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 64 deletions.
13 changes: 7 additions & 6 deletions vehicles/src/common/constraint/application-search.constraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>;
applicationsInQueue?: Nullable<boolean>;
applicationQueueStatus?: Nullable<ApplicationQueueStatus[]>;
searchColumn?: Nullable<ApplicationSearch>;
searchString?: Nullable<string>;
};
if (
fields.pendingPermits != undefined &&
fields.applicationsInQueue !== undefined
fields.applicationQueueStatus?.length
) {
return false;
} else if (fields.searchColumn && !fields.searchString) {
Expand All @@ -35,16 +36,16 @@ export class ApplicationSearchConstraint
const message: string[] = [];
const fields = args.object as {
pendingPermits?: Nullable<boolean>;
applicationsInQueue?: Nullable<boolean>;
applicationQueueStatus?: Nullable<ApplicationQueueStatus[]>;
searchColumn?: Nullable<ApplicationSearch>;
searchString?: Nullable<string>;
};
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) {
Expand Down
44 changes: 44 additions & 0 deletions vehicles/src/common/constraint/query-param-list.constraint.ts
Original file line number Diff line number Diff line change
@@ -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<string>, args: ValidationArguments): boolean {
return !this.validateQueryParamList(field, args)?.length;
}

validateQueryParamList(
field: Nullable<string>,
args: ValidationArguments,
): string[] {
const fieldList = field?.split(',') || [];
const allowedFieldValues = Object.values(
args.constraints?.at(0) as Record<string, unknown>,
);

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<string> }
).applicationQueueStatus;
const invalidFields = this.validateQueryParamList(
applicationQueueStatus,
args,
);
return invalidFields.join(' ');
}
}
2 changes: 1 addition & 1 deletion vehicles/src/common/enum/application-status.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 36 additions & 0 deletions vehicles/src/common/enum/case-status-type.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, CaseStatusType> = {
[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, ApplicationQueueStatus> = {
[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]);
};
6 changes: 2 additions & 4 deletions vehicles/src/common/helper/permit-fee.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.')
Expand Down Expand Up @@ -66,19 +70,20 @@ export class ApplicationController {
orderBy,
searchColumn,
searchString,
applicationsInQueue,
applicationQueueStatus,
}: GetApplicationQueryParamsDto,
): Promise<PaginationDto<ReadApplicationMetadataDto>> {
const currentUser = request.user as IUserJWT;

return await this.applicationService.findAllApplications({
page,
take,
orderBy,
currentUser,
searchColumn,
searchString,
applicationsInQueue,
applicationQueueStatus: convertApplicationQueueStatus(
(applicationQueueStatus?.split(',') as ApplicationQueueStatus[]) || [],
),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -320,7 +320,7 @@ export class ApplicationService {
companyId?: number;
userGUID?: string;
currentUser?: IUserJWT;
applicationsInQueue?: Nullable<boolean>;
applicationQueueStatus?: Nullable<CaseStatusType[]>;
searchColumn?: Nullable<ApplicationSearch>;
searchString?: Nullable<string>;
}): Promise<PaginationDto<ReadApplicationMetadataDto>> {
Expand All @@ -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();
Expand Down Expand Up @@ -389,7 +389,8 @@ export class ApplicationService {
currentUserRole:
findAllApplicationsOptions?.currentUser?.orbcUserRole,
currentDateTime: new Date(),
applicationsInQueue: findAllApplicationsOptions.applicationsInQueue,
applicationQueueStatus:
findAllApplicationsOptions.applicationQueueStatus,
}),
},
);
Expand All @@ -404,12 +405,12 @@ export class ApplicationService {
userGUID?: string,
searchColumn?: Nullable<ApplicationSearch>,
searchString?: Nullable<string>,
applicationsInQueue?: Nullable<boolean>,
applicationQueueStatus?: Nullable<CaseStatusType[]>,
): SelectQueryBuilder<Permit> {
// 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.',
);
}

Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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) {
Expand All @@ -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) => {
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -93,23 +94,20 @@ export class GetApplicationQueryParamsDto extends PageOptionsDto {
pendingPermits?: Nullable<boolean>;

@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: <status1,status2>',
required: false,
type: 'boolean',
type: 'string',
})
@IsOptional()
@Transform(({ obj, key }: { obj: Record<string, unknown>; 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<boolean>;
@Validate(QueryParamListConstraint, [ApplicationQueueStatus])
@IsString()
@Length(1, 150)
applicationQueueStatus?: Nullable<string>;
}
Loading

0 comments on commit 84c5625

Please sign in to comment.