Skip to content

Commit

Permalink
ORV2-3283 - Get Metadata for application in queue (#1760)
Browse files Browse the repository at this point in the history
  • Loading branch information
praju-aot authored Jan 22, 2025
1 parent f6797d6 commit c98724d
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 10 deletions.
69 changes: 69 additions & 0 deletions vehicles/src/modules/case-management/case-management.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ 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';
import { ReadCaseMetaDto } from './dto/response/read-case-meta.dto';

@Injectable()
export class CaseManagementService {
Expand Down Expand Up @@ -351,6 +352,74 @@ export class CaseManagementService {
}
}

/**
* Retrieves metadata for an existing case, ensuring it is available and in an acceptable state.
* If the case does not exist or is already closed, it throws appropriate exceptions.
*
* @param queryRunner - Optional, existing QueryRunner instance.
* @param caseId - Optional, the ID of the case to be queried.
* @param originalCaseId - Optional, the original ID of the case.
* @param applicationId - Optional, the ID of the permit associated with the case.
* @param existingCase - Optional, the existing `Case` entity to be queried.
* @returns A `Promise<ReadCaseMetaDto>` object containing the metadata details of the case.
* @throws `DataNotFoundException` - If the case cannot be found or does not exist.
* @throws `UnprocessableEntityException` - If the case is already closed.
*/
@LogAsyncMethodExecution()
async getCaseMetadata({
queryRunner,
caseId,
originalCaseId,
applicationId,
existingCase,
}: {
queryRunner?: Nullable<QueryRunner>;
caseId?: Nullable<number>;
originalCaseId?: Nullable<number>;
applicationId?: Nullable<string>;
existingCase?: Nullable<Case>;
}): Promise<ReadCaseMetaDto> {
let localQueryRunner = true;
({ localQueryRunner, queryRunner } = await getQueryRunner({
queryRunner,
dataSource: this.dataSource,
}));
try {
if (!existingCase) {
existingCase = await this.findLatest({
queryRunner,
caseId,
originalCaseId,
applicationId,
});
}
if (!existingCase) {
throw new DataNotFoundException();
} else if (existingCase.caseStatusType === CaseStatusType.CLOSED) {
throwUnprocessableEntityException('Application no longer available.');
}

if (localQueryRunner) {
await queryRunner.commitTransaction();
}
return await this.classMapper.mapAsync(
existingCase,
Case,
ReadCaseMetaDto,
);
} catch (error) {
if (localQueryRunner) {
await queryRunner.rollbackTransaction();
}
this.logger.error(error);
throw error;
} finally {
if (localQueryRunner) {
await queryRunner.release();
}
}
}

/**
* Starts the workflow for an existing case by changing its status to `IN_PROGRESS`.
* It first retrieves or verifies the existence of the `Case` based on provided identifiers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,21 @@ import { CaseEventType } from '../../../../common/enum/case-event-type.enum';
export class ReadCaseEvenDto {
@AutoMap()
@ApiProperty({
description:
'The unique identifier for the credit account status update activity linked to this user.',
description: 'The unique identifier of the the event linked to the case.',
example: 4527,
})
caseEventId: number;

@AutoMap()
@ApiProperty({
description: 'The type of event that occurred in the credit account.',
description: 'The type of event that occurred.',
example: CaseEventType.OPENED,
})
caseEventType: CaseEventType;

@AutoMap()
@ApiProperty({
description:
'The date and time when the credit account activity took place.',
description: 'The date and time when the activity took place.',
example: '2023-10-11T23:26:51.170Z',
})
eventDate: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AutoMap } from '@automapper/classes';
import { ApiProperty } from '@nestjs/swagger';
import { CaseType } from '../../../../common/enum/case-type.enum';
import { CaseStatusType } from '../../../../common/enum/case-status-type.enum';

export class ReadCaseMetaDto {
@AutoMap()
@ApiProperty({
description: 'The unique identifier of the case.',
example: 4527,
})
caseId: number;

@AutoMap()
@ApiProperty({
description: 'The type of case.',
example: CaseType.DEFAULT,
})
caseType: CaseType;

@AutoMap()
@ApiProperty({
description: 'The Case Status type.',
example: CaseStatusType.OPEN,
})
caseStatusType: CaseStatusType;

@AutoMap()
@ApiProperty({
description: 'The user name or id linked to the case.',
example: 'JSMITH',
})
assignedUser: string;

@AutoMap()
@ApiProperty({
description: 'Id of the application.',
example: 74,
required: false,
})
applicationId: string;

@AutoMap()
@ApiProperty({
example: 'A2-00000002-120',
description: 'Unique formatted permit application number.',
})
applicationNumber: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ 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';
import { Case } from '../entities/case.entity';
import { ReadCaseMetaDto } from '../dto/response/read-case-meta.dto';

@Injectable()
export class CaseManagementProfile extends AutomapperProfile {
Expand All @@ -25,6 +27,24 @@ export class CaseManagementProfile extends AutomapperProfile {
return (mapper: Mapper) => {
createMap(mapper, CaseEvent, ReadCaseEvenDto);

createMap(
mapper,
Case,
ReadCaseMetaDto,
forMember(
(d) => d.assignedUser,
mapFrom((s) => s?.assignedUser?.userName),
),
forMember(
(d) => d.applicationNumber,
mapFrom((s) => s?.permit?.applicationNumber),
),
forMember(
(d) => d.applicationId,
mapFrom((s) => s?.permit?.permitId),
),
);

createMap(
mapper,
CaseActivity,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { AutoMap } from '@automapper/classes';
import { ApiProperty } from '@nestjs/swagger';
import {
ArrayMinSize,
IsEmail,
IsEnum,
} from 'class-validator';
import { ArrayMinSize, IsEmail, IsEnum } from 'class-validator';
import { NotificationType } from '../../../../common/enum/notification-type.enum';

export class CreateNotificationDto {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { LoaDetail } from 'src/modules/special-auth/entities/loa-detail.entity';
import { getFromCache } from '../../../common/helper/cache.helper';
import { CacheKey } from '../../../common/enum/cache-key.enum';
import { FeatureFlagValue } from '../../../common/enum/feature-flag-value.enum';
import { ReadCaseMetaDto } from '../../case-management/dto/response/read-case-meta.dto';

@Injectable()
export class ApplicationService {
Expand Down Expand Up @@ -609,12 +610,20 @@ export class ApplicationService {
isPermitTypeEligibleForQueue(existingApplication.permitType) &&
existingApplication.permitStatus === ApplicationStatus.IN_QUEUE
) {
const existingCase = await this.caseManagementService.getCaseMetadata({
applicationId,
});

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.');
} else if (existingCase.assignedUser !== currentUser.userName) {
throwUnprocessableEntityException(
`Application no longer available. This application is claimed by ${existingCase.assignedUser}`,
);
}
}

Expand Down Expand Up @@ -1244,6 +1253,49 @@ export class ApplicationService {

return result;
}

/**
* Retrieves metadata for a case linked to an application in queue.
* Before fetching, the function ensures the application's status and type are valid for processing.
*
* Input:
* - @param currentUser: IUserJWT - The user performing the operation.
* - @param companyId: number - The ID of the company associated with the application.
* - @param applicationId: string - The ID of the application to be analyzed.
*
* Output:
* - @returns Promise<ReadCaseMetaDto> - The metadata associated with the case of the application.
*
* Throws exceptions:
* - DataNotFoundException: When the application is not found.
* - UnprocessableEntityException: If the application is ineligible for queue.
*
*/
@LogAsyncMethodExecution()
async getCaseMetadata({
companyId,
applicationId,
}: {
currentUser: IUserJWT;
companyId: number;
applicationId: string;
}): Promise<ReadCaseMetaDto> {
const application = await this.findOne(applicationId, companyId);
if (!application) {
throw new DataNotFoundException();
} else if (!isPermitTypeEligibleForQueue(application.permitType)) {
throwUnprocessableEntityException(
'Invalid permit type. Ineligible for queue.',
);
} else if (application.permitStatus !== ApplicationStatus.IN_QUEUE) {
throwUnprocessableEntityException('Invalid status.');
}
const result = await this.caseManagementService.getCaseMetadata({
applicationId,
});
return result;
}

@LogAsyncMethodExecution()
async createPermitLoa(
currentUser: IUserJWT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Body,
Controller,
ForbiddenException,
Get,
Param,
Post,
Req,
Expand All @@ -12,6 +13,7 @@ import {
ApiInternalServerErrorResponse,
ApiMethodNotAllowedResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiUnprocessableEntityResponse,
Expand All @@ -33,6 +35,7 @@ import { UpdateCaseActivity } from './dto/request/update-case-activity.dto';
import { ReadCaseEvenDto } from '../../case-management/dto/response/read-case-event.dto';
import { ApplicationIdIdPathParamDto } from './dto/request/pathParam/applicationId.path-params.dto';
import { IsFeatureFlagEnabled } from '../../../common/decorator/is-feature-flag-enabled.decorator';
import { ReadCaseMetaDto } from '../../case-management/dto/response/read-case-meta.dto';
@ApiBearerAuth()
@ApiTags('Company Application Queue')
@IsFeatureFlagEnabled('APPLICATION-QUEUE')
Expand Down Expand Up @@ -198,4 +201,47 @@ export class CompanyApplicationQueueController {

return result;
}

/**
* Fetches metadata information for a case identified by the
* application ID. This method enforces authorization checks based on user roles.
*
* @param request - The incoming request containing user information.
* @param companyId - The ID of the company associated with the application.
* @param applicationId - The ID of the application for which the metadata
* information is to be fetched.
* @returns The case metadata information related to the application.
*/
@ApiOperation({
summary: 'Fetch case metadata info for Application',
description:
`Returns the case metadata or throws exceptions if an error is encountered during the retrieval process. ` +
`Accessible only by ${IDIRUserRole.PPC_CLERK}, ${IDIRUserRole.SYSTEM_ADMINISTRATOR}, ${IDIRUserRole.CTPO}`,
})
@ApiOkResponse({
description: 'The retrieved case metadata.',
type: ReadCaseMetaDto,
})
@Permissions({
allowedIdirRoles: [
IDIRUserRole.PPC_CLERK,
IDIRUserRole.SYSTEM_ADMINISTRATOR,
IDIRUserRole.CTPO,
],
})
@Get(':applicationId/queue/meta-data')
async getCaseMetadata(
@Req() request: Request,
@Param() { companyId, applicationId }: ApplicationIdIdPathParamDto,
): Promise<ReadCaseMetaDto> {
const currentUser = request.user as IUserJWT;

const result = await this.applicationService.getCaseMetadata({
currentUser,
companyId,
applicationId,
});

return result;
}
}

0 comments on commit c98724d

Please sign in to comment.