Skip to content

Commit

Permalink
Merge branch 'release/noble-sea-lemon' into CE-1179
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitri-korin-bcps authored Oct 30, 2024
2 parents f6760ae + a96147b commit 8822d02
Show file tree
Hide file tree
Showing 51 changed files with 2,738 additions and 331 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/merge-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jobs:
--set webeoc.pdb.enabled=true
--set nats.config.cluster.replicas=3
--set nats.config.cluster.enabled=true
--set bitnami-pg.backup.cronjob.storage.size=512Mi
--set bitnami-pg.primary.persistence.size=512Mi

promote:
name: Promote Images
Expand Down
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { TeamCodeModule } from "./v1/team_code/team_code.module";
import { OfficerTeamXrefModule } from "./v1/officer_team_xref/officer_team_xref.module";
import { ComplaintMethodReceivedCodeModule } from "./v1/complaint_method_received_code/complaint_method_received_code.module";
import { CompMthdRecvCdAgcyCdXrefModule } from "./v1/comp_mthd_recv_cd_agcy_cd_xref/comp_mthd_recv_cd_agcy_cd_xref.module";
import { LinkedComplaintXrefModule } from "./v1/linked_complaint_xref/linked_complaint_xref.module";

console.log("Var check - POSTGRESQL_HOST", process.env.POSTGRESQL_HOST);
console.log("Var check - POSTGRESQL_DATABASE", process.env.POSTGRESQL_DATABASE);
Expand Down Expand Up @@ -128,6 +129,7 @@ if (process.env.POSTGRESQL_PASSWORD != null) {
OfficerTeamXrefModule,
ComplaintMethodReceivedCodeModule,
CompMthdRecvCdAgcyCdXrefModule,
LinkedComplaintXrefModule,
],
controllers: [AppController],
providers: [AppService, ComplaintSequenceResetScheduler],
Expand Down
2 changes: 2 additions & 0 deletions backend/src/types/models/case-files/assessment-details.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { AssessmentActionDto } from "./assessment-action";
export interface AssessmentDetailsDto {
actionNotRequired: boolean;
actionCloseComplaint: boolean;
actionLinkedComplaintIdentifier: string;
actionJustificationCode: string;
actionJustificationShortDescription: string;
actionJustificationLongDescription: string;
Expand Down
11 changes: 10 additions & 1 deletion backend/src/v1/case_file/case_file.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { AutomapperModule } from "@automapper/nestjs";
import { CaseFileController } from "./case_file.controller";
import { CaseFileService } from "./case_file.service";
import { CodeTableModule } from "../code-table/code-table.module";
import { ComplaintModule } from "../complaint/complaint.module";
import { Complaint } from "../complaint/entities/complaint.entity";
import { LinkedComplaintXref } from "../linked_complaint_xref/entities/linked_complaint_xref.entity";

@Module({
imports: [AutomapperModule, CodeTableModule, ComplaintModule],
imports: [
AutomapperModule,
CodeTableModule,
ComplaintModule,
TypeOrmModule.forFeature([Complaint]),
TypeOrmModule.forFeature([LinkedComplaintXref]),
],
controllers: [CaseFileController],
providers: [CaseFileService],
})
Expand Down
5 changes: 5 additions & 0 deletions backend/src/v1/case_file/case_file.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { StagingComplaint } from "../staging_complaint/entities/staging_complain
import { TeamCode } from "../team_code/entities/team_code.entity";
import { CompMthdRecvCdAgcyCdXref } from "../comp_mthd_recv_cd_agcy_cd_xref/entities/comp_mthd_recv_cd_agcy_cd_xref";
import { CompMthdRecvCdAgcyCdXrefService } from "../comp_mthd_recv_cd_agcy_cd_xref/comp_mthd_recv_cd_agcy_cd_xref.service";
import { LinkedComplaintXref } from "../linked_complaint_xref/entities/linked_complaint_xref.entity";

describe("Testing: Case File Service", () => {
let service: CaseFileService;
Expand Down Expand Up @@ -187,6 +188,10 @@ describe("Testing: Case File Service", () => {
provide: getRepositoryToken(CompMthdRecvCdAgcyCdXref),
useFactory: MockCompMthdRecvCdAgcyCdXrefRepository,
},
{
provide: getRepositoryToken(LinkedComplaintXref),
useValue: {},
},
ComplaintUpdatesService,
CaseFileService,
ComplaintService,
Expand Down
150 changes: 140 additions & 10 deletions backend/src/v1/case_file/case_file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import { UpdateDecisionInput } from "src/types/models/case-files/ceeb/decision/u
import { CreateAuthorizationOutcomeInput } from "src/types/models/case-files/ceeb/site/create-authorization-outcome-input";
import { UpdateAuthorizationOutcomeInput } from "src/types/models/case-files/ceeb/site/update-authorization-outcome-input";
import { DeleteAuthorizationOutcomeInput } from "src/types/models/case-files/ceeb/site/delete-authorization-outcome-input";
import { DataSource, Repository } from "typeorm";
import { LinkedComplaintXref } from "../linked_complaint_xref/entities/linked_complaint_xref.entity";
import { InjectRepository } from "@nestjs/typeorm";
import { getIdirFromRequest } from "../../common/get-idir-from-request";
import { CodeTableService } from "../code-table/code-table.service";
import { Complaint } from "../complaint/entities/complaint.entity";
import { CreateLinkedComplaintXrefDto } from "../linked_complaint_xref/dto/create-linked_complaint_xref.dto";

@Injectable({ scope: Scope.REQUEST })
export class CaseFileService {
Expand Down Expand Up @@ -153,6 +160,8 @@ export class CaseFileService {
@Inject(REQUEST) private request: Request,
@InjectMapper() mapper,
private readonly complaintService: ComplaintService,
private readonly _codeTableService: CodeTableService,
private readonly dataSource: DataSource,
) {
this.mapper = mapper;
}
Expand All @@ -178,17 +187,138 @@ export class CaseFileService {
}
};

createAssessment = async (token: string, model: CaseFileDto): Promise<CaseFileDto> => {
const result = await post(token, {
query: `mutation CreateAssessment($createAssessmentInput: CreateAssessmentInput!) {
createAssessment(createAssessmentInput: $createAssessmentInput)
${this.caseFileQueryFields}
}`,
variables: model,
});
const returnValue = await this.handleAPIResponse(result);
// The linked complaint xref and complaint repositories are needed if an assessment being created is also linking two
// complaints. The codeTableService is needed to fetch the status used to update the closing complaint's status.
@InjectRepository(LinkedComplaintXref)
private readonly _linkedComplaintXrefRepository: Repository<LinkedComplaintXref>;
@InjectRepository(Complaint)
private readonly _complaintsRepository: Repository<Complaint>;

async createAssessment(token: string, model: CaseFileDto): Promise<CaseFileDto> {
/**
* If the assessment is linking the complaint to another, the assessment (CM db) and a linked complaint xref
* (NATCom db) both need to be created. To ensure that either both are successfully created, or neither of them
* are, the following takes place:
* A transaction is started in the NATCom database. If anything beyond this point fails, the transaction is
* rolled back. With the transaction open, the db's ability to fulfill the creation of the link is confirmed.
* Once confirmed, the assessment is created in the CM database. If this is successful, the transaction in the
* NATCom database is commit, the connection is closed, and the new assessmentis returned.
*
* This process of creating the complaint links is handled here to accommodate this pseudo two phase commit
* pattern.
*/

let returnValue;
// The model reaches this function in the shape { "createAssessmentInput": {...CaseFlieDTO} } despite that property
// not existing in the CaseFileDTO type, which renders the CaseFile fields inside inaccessible in this scope.
// For example, leadIdentifier would be found in model.leadIdentifier by the type's definition, however in this
// scope it is at model.createAssessmentInput.leadIdentifier, which errors due to type violation.
// This copies it into a new variable cast to any to allow access to the nested properties.
let modelAsAny: any = { ...model };
// If changes need to be made in both databases (i.e. we need to create a link or change the status of a complaint)
// then the transactional approach is taken.
if (
modelAsAny.createAssessmentInput.assessmentDetails.actionLinkedComplaintIdentifier ||
modelAsAny.createAssessmentInput.assessmentDetails.actionCloseComplaint
) {
const dateLinkCreated = new Date();
const complaintBeingLinkedId = modelAsAny.createAssessmentInput.leadIdentifier;
const linkingToComplaintId = modelAsAny.createAssessmentInput.assessmentDetails.actionLinkedComplaintIdentifier;
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const idir = getIdirFromRequest(this.request);
// If actionLinkedComplaintIdentifier is present the link must be created in the database
if (modelAsAny.createAssessmentInput.assessmentDetails.actionLinkedComplaintIdentifier) {
// When linking complaint "A" to complaint "B", if "A" already has other complaints linked to it those links
// are marked as inactive, and new links are created with them pointing to "B".
const trailingComplaints = await this._linkedComplaintXrefRepository
.createQueryBuilder()
.update(LinkedComplaintXref)
.set({ active_ind: false })
.where({
complaint_identifier: complaintBeingLinkedId,
})
.andWhere({
active_ind: true,
})
.returning("*")
.execute();

if (trailingComplaints.affected > 0) {
trailingComplaints.raw.forEach(async (row) => {
const linkString = await this._linkedComplaintXrefRepository.create(<CreateLinkedComplaintXrefDto>{
// ...row,
complaint_identifier: { complaint_identifier: linkingToComplaintId },
linked_complaint_identifier: row.linked_complaint_identifier,
active_ind: true,
create_user_id: idir,
create_utc_timestamp: dateLinkCreated,
});
await queryRunner.manager.save(linkString);
});
}

// Create the new link between the complaints
let newLink = new CreateLinkedComplaintXrefDto();
newLink = {
...newLink,
complaint_identifier: <Complaint>{
complaint_identifier: linkingToComplaintId,
},
linked_complaint_identifier: <Complaint>{ complaint_identifier: complaintBeingLinkedId },
active_ind: true,
create_user_id: idir,
create_utc_timestamp: dateLinkCreated,
};

const complaintLinkString = await this._linkedComplaintXrefRepository.create(newLink);
await queryRunner.manager.save(complaintLinkString);
}
// Update the status of the complaint to "closed" if actionCloseComplaint is set to true
if (modelAsAny.createAssessmentInput.assessmentDetails.actionCloseComplaint) {
const statusCode = await this._codeTableService.getComplaintStatusCodeByStatus("CLOSED");
await this._complaintsRepository
.createQueryBuilder("complaint")
.update()
.set({ complaint_status_code: statusCode, update_user_id: idir })
.where({ complaint_identifier: complaintBeingLinkedId })
.execute();
}

// Create the assessment in the Case Management database
const result = await post(token, {
query: `mutation CreateAssessment($createAssessmentInput: CreateAssessmentInput!) {
createAssessment(createAssessmentInput: $createAssessmentInput)
${this.caseFileQueryFields}
}`,
variables: model,
});
returnValue = await this.handleAPIResponse(result);
// If the mutation succeeded, commit the pending transaction
await queryRunner.commitTransaction();
} catch (err) {
this.logger.error(err);
await queryRunner.rollbackTransaction();
Promise.reject(new Error("An error occurred while linking the complaints. " + err));
} finally {
await queryRunner.release();
}
} else {
// If the assessment is not linking two complaints then simply create the new assessment in CM.
const result = await post(token, {
query: `mutation CreateAssessment($createAssessmentInput: CreateAssessmentInput!) {
createAssessment(createAssessmentInput: $createAssessmentInput)
${this.caseFileQueryFields}
}`,
variables: model,
});
returnValue = await this.handleAPIResponse(result);
}

return returnValue?.createAssessment;
};
}

updateAssessment = async (token: string, model: CaseFileDto): Promise<CaseFileDto> => {
const result = await post(token, {
Expand Down
18 changes: 17 additions & 1 deletion backend/src/v1/complaint/complaint.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { dtoAlias } from "../../types/models/complaints/dtoAlias-type";
import { RelatedDataDto } from "src/types/models/complaints/related-data";
import { ACTION_TAKEN_ACTION_TYPES } from "src/types/constants";
import { hasRole } from "src/common/has-role";
import { LinkedComplaintXrefService } from "../linked_complaint_xref/linked_complaint_xref.service";

@UseGuards(JwtRoleGuard)
@ApiTags("complaint")
Expand All @@ -29,7 +30,11 @@ import { hasRole } from "src/common/has-role";
version: "1",
})
export class ComplaintController {
constructor(private readonly service: ComplaintService, private readonly stagingService: StagingComplaintService) {}
constructor(
private readonly service: ComplaintService,
private readonly stagingService: StagingComplaintService,
private readonly linkedComplaintXrefService: LinkedComplaintXrefService,
) {}
private readonly logger = new Logger(ComplaintController.name);

@Get(":complaintType")
Expand Down Expand Up @@ -122,6 +127,17 @@ export class ComplaintController {
return this.service.getZoneAtAGlanceStatistics(complaintType, zone);
}

@Get("/linked-complaints/:complaint_id")
@Roles(Role.COS_OFFICER)
async findLinkedComplaintsById(@Param("complaint_id") complaintId: string) {
const childComplaints = await this.linkedComplaintXrefService.findChildComplaints(complaintId);
if (childComplaints.length > 0) return childComplaints;
else {
const parentComplaint = await this.linkedComplaintXrefService.findParentComplaint(complaintId);
return parentComplaint;
}
}

@Public()
@Post("/staging/action-taken")
@UseGuards(ApiKeyGuard)
Expand Down
2 changes: 2 additions & 0 deletions backend/src/v1/complaint/complaint.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { ComplaintMethodReceivedCode } from "../complaint_method_received_code/e
import { CompMthdRecvCdAgcyCdXref } from "../comp_mthd_recv_cd_agcy_cd_xref/entities/comp_mthd_recv_cd_agcy_cd_xref";
import { CompMthdRecvCdAgcyCdXrefService } from "../comp_mthd_recv_cd_agcy_cd_xref/comp_mthd_recv_cd_agcy_cd_xref.service";
import { CompMthdRecvCdAgcyCdXrefModule } from "../comp_mthd_recv_cd_agcy_cd_xref/comp_mthd_recv_cd_agcy_cd_xref.module";
import { LinkedComplaintXrefModule } from "../linked_complaint_xref/linked_complaint_xref.module";

@Module({
imports: [
Expand Down Expand Up @@ -65,6 +66,7 @@ import { CompMthdRecvCdAgcyCdXrefModule } from "../comp_mthd_recv_cd_agcy_cd_xre
ComplaintUpdatesModule,
StagingComplaintModule,
CompMthdRecvCdAgcyCdXrefModule,
LinkedComplaintXrefModule,
],
controllers: [ComplaintController],
providers: [ComplaintService, CompMthdRecvCdAgcyCdXrefService],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PickType } from "@nestjs/swagger";
import { LinkedComplaintXrefDto } from "./linked_complaint_xref.dto";

export class CreateLinkedComplaintXrefDto extends PickType(LinkedComplaintXrefDto, [
"create_user_id",
"create_utc_timestamp",
"update_user_id",
"update_utc_timestamp",
"complaint_identifier",
"linked_complaint_identifier",
"active_ind",
] as const) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ApiProperty } from "@nestjs/swagger";
import { UUID } from "crypto";
import { Complaint } from "../../complaint/entities/complaint.entity";

export class LinkedComplaintXrefDto {
@ApiProperty({
example: "903f87c8-76dd-427c-a1bb-4d179e443252",
description:
"System generated unique key for a linked complaint relationship. This key should never be exposed to users via any system utilizing the tables.",
})
public linked_complaint_xref_guid: UUID;

@ApiProperty({
example: "mburns",
description: "The id of the user that created the cross reference.",
})
create_user_id: string;

@ApiProperty({
example: "2003-04-12 04:05:06",
description: "The timestamp when the cross reference was created. The timestamp is stored in UTC with no Offset.",
})
create_utc_timestamp: Date;

@ApiProperty({
example: "mburns",
description: "The id of the user that updated the cross reference.",
})
update_user_id: string;

@ApiProperty({
example: "2003-04-12 04:05:06",
description: "The timestamp when the cross reference was updated. The timestamp is stored in UTC with no Offset.",
})
update_utc_timestamp: Date;

@ApiProperty({
example: "24-007007",
description: "System generated unique key for a hwcr complaint.",
})
public linked_complaint_identifier: Complaint;

@ApiProperty({
example: "23-007007",
description: "System generated unique key for a hwcr complaint.",
})
public complaint_identifier: Complaint;

@ApiProperty({
example: "True",
description: "A boolean indicator to determine if the linked complaint is active.",
})
public active_ind: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from "@nestjs/mapped-types";
import { CreateLinkedComplaintXrefDto } from "./create-linked_complaint_xref.dto";

export class UpdateLinkedComplaintXrefDto extends PartialType(CreateLinkedComplaintXrefDto) {}
Loading

0 comments on commit 8822d02

Please sign in to comment.