Skip to content

Commit

Permalink
feat: ORV2-2973 - Application Queue resubmit and rejection (#1592)
Browse files Browse the repository at this point in the history
  • Loading branch information
praju-aot authored Sep 12, 2024
1 parent 23fea88 commit b5cf05e
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ 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;
}

return true;
}

defaultMessage() {
return `Comment is required when activity type is ${CaseActivityType.APPROVED} or ${CaseActivityType.REJECTED} `;
return `Comment is required when activity type is ${CaseActivityType.REJECTED} `;
}
}
97 changes: 64 additions & 33 deletions vehicles/src/modules/case-management/case-management.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -32,6 +33,8 @@ export class CaseManagementService {
private dataSource: DataSource,
@InjectRepository(Case)
private readonly caseRepository: Repository<Case>,
@InjectRepository(CaseActivity)
private readonly caseActivityRepository: Repository<CaseActivity>,
) {}

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.',
);
}

Expand All @@ -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<CaseActivity>(newActivity);

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<ReadCaseActivityDto[]>` containing the list of activities for the specified case.
*/
@LogAsyncMethodExecution()
async fetchActivityHistory({
currentUser,
applicationId,
caseActivityType,
}: {
currentUser: IUserJWT;
applicationId: Nullable<string>;
caseActivityType: CaseActivityType;
}): Promise<ReadCaseActivityDto[]> {
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;
}
}
Original file line number Diff line number Diff line change
@@ -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<string>;
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;
}
},
),
),
);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -270,13 +271,24 @@ export class ApplicationService {
companyId?: number,
): Promise<ReadApplicationDto> {
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,
ReadApplicationDto,
{
extraArgs: () => ({
currentUserRole: currentUser?.orbcUserRole,
readCaseActivityList: readCaseActivityList,
}),
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -128,4 +129,12 @@ export class ReadApplicationDto {
required: false,
})
applicant: string;

@AutoMap()
@ApiProperty({
description: 'Application rejection history',
type: [ReadCaseActivityDto],
required: false,
})
rejectionHistory?: ReadCaseActivityDto[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -207,6 +208,21 @@ export class ApplicationProfile extends AutomapperProfile {
}
}),
),
forMember(
(d) => d.rejectionHistory,
mapWithArguments(
(
s,
{
readCaseActivityList,
}: { readCaseActivityList: ReadCaseActivityDto[] },
) => {
if (readCaseActivityList?.length) {
return readCaseActivityList;
}
},
),
),
);

createMap(
Expand Down

0 comments on commit b5cf05e

Please sign in to comment.