Skip to content

Commit

Permalink
#3183 - Generate eCert Feedback File Error Ministry Notification (#3489)
Browse files Browse the repository at this point in the history
### As a part of this PR, the following were completed:

- Created a new GC Notify template.
- Created the DB migrations for the new email notification.
- Created the SQL to manually update the PROD email address and added
the manual instruction to the release.
- Added the following e2e tests:
  Scheduler queue processor for part-time-feedback-integration.
√ Should not generate a notification to the ministry when there are
ecert feedback errors but none of them block funding for the
disbursement.
  Scheduler queue processor for full-time-feedback-integration.
√ Should generate a notification to the ministry when there are ecert
feedback errors that block funding for a disbursement.

**Note: The code logic is updated to create a ministry notification if
at least one of the error codes in the eCert feedback response record
for a disbursement blocks funding.**

### Notification Email Screenshot:

<img width="1339" alt="image"
src="https://github.com/bcgov/SIMS/assets/7859295/2c26302d-b8bb-4348-8610-1f69f24ad763">

### e2e Tests Screenshot:
<img width="1179" alt="image"
src="https://github.com/user-attachments/assets/187ad9ee-b91d-42e2-9a7e-782706c74c27">

<img width="1172" alt="image"
src="https://github.com/user-attachments/assets/9572278a-1471-49f0-98c6-4d1cab5c1784">
  • Loading branch information
sh16011993 authored Jul 16, 2024
1 parent 9f1f586 commit b6db791
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { getSQLFileData } from "../utilities/sqlLoader";

export class InsertEcertFeedbackFileErrorMinistryMessage1720187973379
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData(
"Insert-ecert-feedback-file-error-message.sql",
"NotificationMessages",
),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData(
"Rollback-insert-ecert-feedback-file-error-message.sql",
"NotificationMessages",
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
INSERT INTO
sims.notification_messages(id, description, template_id)
VALUES
(
27,
'Ministry notification for eCert feedback file error.',
'1d8f7d12-3a5a-4961-840d-e1a2558ceb45'
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DELETE FROM
sims.notification_messages
WHERE
ID = 27;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
100222 NEW ENTITLEMENT FEEDBACK 202403201254000003
20039980014300000000020230010510000666620240311202403192024031100204400189100000153000000000000002024030920241207BPTW40182200000000F 20240318E20040713Fish John 2279 Shaugnessy St Port Coquitlam BC V3C4S7 Canada [email protected] OM E002044CSGD001642CSGF000402 EDU-00033 EDU-00034
999 NEW ENTITLEMENT FEEDBACK 000000001000000001000000399800143
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
01 222NEW PT ENTITLEMENT FEEDBACK 000000004202403201254
02FNAM LSNAME 399800085O19570211SI 2525 DONSTREET Victoria BC CAN V8W2H9 2024031100000124800077772024030920240720BPTW18 402020240319 0000000000000000000047800478000000000000000000000000000000 [email protected] P20240318005000050N EDU-00127
99NEW PT ENTITLEMENT FEEDBACK 000000005000000001000000399800085
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ApplicationStatus,
COEStatus,
DisbursementScheduleStatus,
NotificationMessageType,
OfferingIntensity,
} from "@sims/sims-db";
import {
Expand All @@ -25,8 +26,10 @@ import { DeepMocked } from "@golevelup/ts-jest";
import * as Client from "ssh2-sftp-client";
import * as path from "path";
import { FullTimeECertFeedbackIntegrationScheduler } from "../ecert-full-time-feedback-integration.scheduler";
import { IsNull } from "typeorm";

const FEEDBACK_ERROR_FILE_SINGLE_RECORD = "EDU.PBC.FTECERTSFB.SINGLERECORD";
const FEEDBACK_ERROR_NOTIFICATION_FILE = "EDU.PBC.FTECERTSFB.ERRORNOTIFICATION";
const FEEDBACK_ERROR_FILE_MULTIPLE_RECORDS =
"EDU.PBC.FTECERTSFB.MULTIPLERECORDS";
const SHARED_DOCUMENT_NUMBER = 6666;
Expand All @@ -53,6 +56,13 @@ describe(
sftpClientMock = sshClientMock;
// Processor under test.
processor = app.get(FullTimeECertFeedbackIntegrationScheduler);
// Insert fake email contact to send ministry email.
await db.notificationMessage.update(
{
id: NotificationMessageType.ECertFeedbackFileErrorNotification,
},
{ emailContacts: ["[email protected]"] },
);
});

beforeEach(async () => {
Expand Down Expand Up @@ -385,5 +395,87 @@ describe(
});
},
);

it("Should generate a notification to the ministry when there are ecert feedback errors that block funding for a disbursement.", async () => {
// Arrange
// Update the date sent for the notifications to current date where the date sent is null.
await db.notification.update(
{ dateSent: IsNull() },
{ dateSent: new Date() },
);
const application = await saveFakeApplicationDisbursements(
db.dataSource,
undefined,
{
offeringIntensity: OfferingIntensity.fullTime,
applicationStatus: ApplicationStatus.Completed,
firstDisbursementInitialValues: {
coeStatus: COEStatus.completed,
disbursementScheduleStatus: DisbursementScheduleStatus.Sent,
documentNumber: SHARED_DOCUMENT_NUMBER,
},
},
);
mockDownloadFiles(
sftpClientMock,
[FEEDBACK_ERROR_FILE_SINGLE_RECORD],
(fileContent: string) => {
const file = getStructuredRecords(fileContent);
// Force the error code to be wrong in the first record.
const [record] = file.records;
file.records = [
record.replace("EDU-00099 ", "EDU-00033 EDU-00034"),
];
return createFileFromStructuredRecords(file);
},
);
// Queued job.
const mockedJob = mockBullJob<void>();

// Act
const result = await processor.processFullTimeResponses(mockedJob.job);

// Assert
expect(result.length).toBe(1);
expect(result).toContain("Process finalized with success.");
expect(
mockedJob.containLogMessages([
"File contains 1 records.",
`Disbursement feedback error created for document number ${SHARED_DOCUMENT_NUMBER} at line 2.`,
]),
).toBe(true);
// When all the records are processed successfully, expect the file to be deleted from SFTP.
expect(sftpClientMock.delete).toHaveBeenCalled();

// Assert
const notification = await db.notification.findOne({
select: {
id: true,
messagePayload: true,
notificationMessage: {
id: true,
templateId: true,
emailContacts: true,
},
},
relations: { notificationMessage: true },
where: {
notificationMessage: {
id: NotificationMessageType.ECertFeedbackFileErrorNotification,
},
dateSent: IsNull(),
},
});
expect(notification.messagePayload).toStrictEqual({
email_address: notification.notificationMessage.emailContacts[0],
template_id: notification.notificationMessage.templateId,
personalisation: {
lastName: application.student.user.lastName,
givenNames: application.student.user.firstName,
applicationNumber: application.applicationNumber,
errorCodes: ["EDU-00033", "EDU-00034"],
},
});
});
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ApplicationStatus,
COEStatus,
DisbursementScheduleStatus,
NotificationMessageType,
OfferingIntensity,
} from "@sims/sims-db";
import {
Expand All @@ -25,11 +26,14 @@ import { DeepMocked } from "@golevelup/ts-jest";
import * as Client from "ssh2-sftp-client";
import * as path from "path";
import { PartTimeECertFeedbackIntegrationScheduler } from "../ecert-part-time-feedback-integration.scheduler";
import { IsNull } from "typeorm";

const FEEDBACK_ERROR_FILE_SINGLE_RECORD =
"EDU.PBC.NEW.ECERTSFB.PT.SINGLERECORD";
const FEEDBACK_ERROR_FILE_MULTIPLE_RECORDS =
"EDU.PBC.NEW.ECERTSFB.PT.MULTIPLERECORDS";
const FEEDBACK_ERROR_NON_NOTIFICATION_FILE =
"EDU.PBC.NEW.ECERTSFB.PT.ERRORNONNOTIFICATION";
const SHARED_DOCUMENT_NUMBER = 7777;
const SHARED_ERROR_CODE = "EDU-00128";

Expand Down Expand Up @@ -386,5 +390,52 @@ describe(
});
},
);

it("Should not generate a notification to the ministry when there are ecert feedback errors but none of them block funding for the disbursement.", async () => {
// Arrange
// Update the date sent for the notifications to current date where the date sent is null.
await db.notification.update(
{ dateSent: IsNull() },
{ dateSent: new Date() },
);
await saveFakeApplicationDisbursements(db.dataSource, undefined, {
offeringIntensity: OfferingIntensity.partTime,
applicationStatus: ApplicationStatus.Completed,
firstDisbursementInitialValues: {
coeStatus: COEStatus.completed,
disbursementScheduleStatus: DisbursementScheduleStatus.Sent,
documentNumber: SHARED_DOCUMENT_NUMBER,
},
});
mockDownloadFiles(sftpClientMock, [FEEDBACK_ERROR_NON_NOTIFICATION_FILE]);
// Queued job.
const mockedJob = mockBullJob<void>();

// Act
const result = await processor.processPartTimeResponses(mockedJob.job);

// Assert
expect(result.length).toBe(1);
expect(result).toContain("Process finalized with success.");
expect(
mockedJob.containLogMessages([
"File contains 1 records.",
`Disbursement feedback error created for document number ${SHARED_DOCUMENT_NUMBER} at line 2.`,
]),
).toBe(true);
// When all the records are processed successfully, expect the file to be deleted from SFTP.
expect(sftpClientMock.delete).toHaveBeenCalled();

// Assert
const notificationCount = await db.notification.count({
where: {
notificationMessage: {
id: NotificationMessageType.ECertFeedbackFileErrorNotification,
},
dateSent: IsNull(),
},
});
expect(notificationCount).toBe(0);
});
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,20 @@ import * as path from "path";
const ECERT_GENERATION_NO_RECORDS_AVAILABLE =
"ECERT_GENERATION_NO_RECORDS_AVAILABLE";

/**
* Error details: error id and block funding info
* for each error code.
*/
interface ErrorDetails {
id: number;
blockFunding: boolean;
}

/**
* ECert feedback error map with error code and
* e-Cert feedback error id.
* e-Cert feedback error details.
*/
type ECertFeedbackCodeMap = Record<string, number>;
type ECertFeedbackCodeMap = Record<string, ErrorDetails>;

export abstract class ECertFileHandler extends ESDCFileHandler {
esdcConfig: ESDCIntegrationConfig;
Expand Down Expand Up @@ -388,8 +397,10 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
}

/**
* Create disbursement feedback errors
* for the errors received in a response record.
* Create disbursement feedback errors for the errors received
* in a response record and sends a ministry notification if
* at least one of the error codes in the eCert feedback response
* record for a disbursement blocks funding.
* @param processSummary process summary of record processing.
* @param eCertFeedbackResponseRecord e-Cert feedback response record.
* @param eCertFeedbackErrorCodeMap e-Cert feedback error map
Expand All @@ -413,13 +424,22 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
eCertFeedbackResponseRecord.errorCode5,
]
.filter((errorCode) => errorCode)
.map((errorCode) => eCertFeedbackErrorCodeMap[errorCode]);

.map((errorCode) => eCertFeedbackErrorCodeMap[errorCode].id);
const blockFundingErrorCodes = [
eCertFeedbackResponseRecord.errorCode1,
eCertFeedbackResponseRecord.errorCode2,
eCertFeedbackResponseRecord.errorCode3,
eCertFeedbackResponseRecord.errorCode4,
eCertFeedbackResponseRecord.errorCode5,
].filter(
(errorCode) => eCertFeedbackErrorCodeMap[errorCode]?.blockFunding,
);
await this.disbursementScheduleErrorsService.createECertErrorRecord(
eCertFeedbackResponseRecord.documentNumber,
feedbackFileName,
receivedErrorIds,
dateReceived,
blockFundingErrorCodes,
);
processSummary.info(
`Disbursement feedback error created for document number ${eCertFeedbackResponseRecord.documentNumber} at line ${eCertFeedbackResponseRecord.lineNumber}.`,
Expand Down Expand Up @@ -448,8 +468,10 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
);
const eCertFeedbackErrorCodeMap: ECertFeedbackCodeMap = {};
for (const eCertFeedbackError of eCertFeedbackErrors) {
eCertFeedbackErrorCodeMap[eCertFeedbackError.errorCode] =
eCertFeedbackError.id;
eCertFeedbackErrorCodeMap[eCertFeedbackError.errorCode] = {
id: eCertFeedbackError.id,
blockFunding: eCertFeedbackError.blockFunding,
};
}
return eCertFeedbackErrorCodeMap;
}
Expand Down
Loading

0 comments on commit b6db791

Please sign in to comment.