diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-full-time-process-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-full-time-process-integration.scheduler.e2e-spec.ts index 21ecac1d9a..e4bcb6a217 100644 --- a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-full-time-process-integration.scheduler.e2e-spec.ts +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-full-time-process-integration.scheduler.e2e-spec.ts @@ -174,8 +174,7 @@ describe( // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -196,6 +195,30 @@ describe( // TODO Add other fields as needed. }); + it("Should generate an e-cert file with only header and footer when there is no disbursement to be sent.", async () => { + // Queued job. + const { job } = mockBullJob(); + + // Act + const result = await processor.processQueue(job); + + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); + expect(result).toStrictEqual([ + `Generated file: ${uploadedFileName}`, + "Uploaded records: 0", + ]); + + // Assert header and footer. + const [header, footer] = uploadedFile.fileLines; + // Validate header. + expect(header).toContain("100BC NEW ENTITLEMENT"); + // Validate footer. + expect(footer.substring(0, 3)).toBe("999"); + }); + it("Should execute overawards deductions and calculate awards effective value", async () => { // Arrange @@ -266,12 +289,9 @@ describe( // Act const result = await processor.processQueue(job); - // Assert - // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -511,12 +531,8 @@ describe( // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - expect(uploadedFile.remoteFilePath).toBe( - `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`, - ); expect(uploadedFile.fileLines).toHaveLength(5); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -698,12 +714,8 @@ describe( // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - expect(uploadedFile.remoteFilePath).toBe( - `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`, - ); + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.fileLines).toHaveLength(3); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`; expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -791,9 +803,12 @@ describe( // Act const result = await processor.processQueue(job); - // Assert 0 uploaded records. + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); const [disbursement] = @@ -864,8 +879,7 @@ describe( // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -933,8 +947,7 @@ describe( // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -1336,9 +1349,13 @@ describe( // Act const result = await processor.processQueue(mockedJob.job); + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); // Assert expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); expect( @@ -1381,5 +1398,15 @@ describe( ]); }, ); + + /** + * Helper function to get the uploaded file name. + * @returns The uploaded file name + */ + function getUploadedFileName() { + const fileDate = dayjs().format("YYYYMMDD"); + const uploadedFileName = `MSFT-Request\\DPBC.EDU.FTECERTS.${fileDate}.001`; + return uploadedFileName; + } }, ); diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-part-time-process-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-part-time-process-integration.scheduler.e2e-spec.ts index df98383e4b..f435d489bd 100644 --- a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-part-time-process-integration.scheduler.e2e-spec.ts +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/esdc-integration/ecert-integration/_tests_/ecert-part-time-process-integration.scheduler.e2e-spec.ts @@ -113,9 +113,14 @@ describe( // Act const result = await processor.processQueue(mockedJob.job); + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); + // Assert expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); expect( @@ -173,9 +178,13 @@ describe( // Act const result = await processor.processQueue(mockedJob.job); + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); // Assert expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); expect( @@ -233,9 +242,14 @@ describe( // Act const result = await processor.processQueue(mockedJob.job); + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); + // Assert expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); const notificationsCount = await db.notification.count({ @@ -262,9 +276,14 @@ describe( // Act const result = await processor.processQueue(mockedJob.job); + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); + // Assert expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); const notificationsCount = await db.notification.count({ @@ -294,9 +313,14 @@ describe( // Act const result = await processor.processQueue(mockedJob.job); + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); + // Assert expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); expect( @@ -366,12 +390,9 @@ describe( // Act const result = await processor.processQueue(job); - // Assert - // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.NEW.PTCERTS.D${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -408,6 +429,30 @@ describe( expect(scheduleIsSent).toBe(true); }); + it("Should generate an e-cert file with only header and footer when there is no disbursement to be sent.", async () => { + // Queued job. + const { job } = mockBullJob(); + + // Act + const result = await processor.processQueue(job); + + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); + expect(result).toStrictEqual([ + `Generated file: ${uploadedFileName}`, + "Uploaded records: 0", + ]); + + // Assert header and footer + const [header, footer] = uploadedFile.fileLines; + // Validate header. + expect(header).toContain("01BC NEW PT ENTITLEMENT"); + // Validate footer. + expect(footer.substring(0, 2)).toBe("99"); + }); + it("Should create an e-Cert with valid student profile data when the student has necessary profile data and gender defined as 'Prefer not to answer'.", async () => { // Arrange // Student with valid SIN. @@ -552,8 +597,7 @@ describe( // Assert // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.NEW.PTCERTS.D${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -680,12 +724,9 @@ describe( // Act const result = await processor.processQueue(job); - // Assert - // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.NEW.PTCERTS.D${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -792,9 +833,14 @@ describe( // Act const result = await processor.processQueue(job); + // Assert uploaded file. + const uploadedFile = getUploadedFile(sftpClientMock); + const uploadedFileName = getUploadedFileName(); + expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); + // Assert expect(result).toStrictEqual([ - "Generated file: none", + `Generated file: ${uploadedFileName}`, "Uploaded records: 0", ]); const [disbursement] = @@ -906,8 +952,7 @@ describe( // Assert // Assert uploaded file. const uploadedFile = getUploadedFile(sftpClientMock); - const fileDate = dayjs().format("YYYYMMDD"); - const uploadedFileName = `MSFT-Request\\DPBC.EDU.NEW.PTCERTS.D${fileDate}.001`; + const uploadedFileName = getUploadedFileName(); expect(uploadedFile.remoteFilePath).toBe(uploadedFileName); expect(result).toStrictEqual([ `Generated file: ${uploadedFileName}`, @@ -1443,5 +1488,15 @@ describe( }), ).toBe(true); }); + + /** + * Helper function to get the uploaded file name. + * @returns The uploaded file name + */ + function getUploadedFileName() { + const fileDate = dayjs().format("YYYYMMDD"); + const uploadedFileName = `MSFT-Request\\DPBC.EDU.NEW.PTCERTS.D${fileDate}.001`; + return uploadedFileName; + } }, ); diff --git a/sources/packages/backend/libs/integrations/src/esdc-integration/e-cert-integration/e-cert-file-handler.ts b/sources/packages/backend/libs/integrations/src/esdc-integration/e-cert-integration/e-cert-file-handler.ts index e35c2ebc03..6a8186efaf 100644 --- a/sources/packages/backend/libs/integrations/src/esdc-integration/e-cert-integration/e-cert-file-handler.ts +++ b/sources/packages/backend/libs/integrations/src/esdc-integration/e-cert-integration/e-cert-file-handler.ts @@ -9,12 +9,7 @@ import { ECertFeedbackErrorService, } from "../../services"; import { SequenceControlService, SystemUsersService } from "@sims/services"; -import { - CustomNamedError, - getISODateOnlyString, - parseJSONError, - processInParallel, -} from "@sims/utilities"; +import { getISODateOnlyString, processInParallel } from "@sims/utilities"; import { EntityManager } from "typeorm"; import { ESDCFileHandler } from "../esdc-file-handler"; import { @@ -28,14 +23,6 @@ import { ECertGenerationService } from "@sims/integrations/services"; import { ECertResponseRecord } from "./e-cert-files/e-cert-response-record"; import * as path from "path"; -/** - * Used to abort the e-Cert generation process, cancel the current transaction, - * and let the consumer method know that it was aborted because no records are - * present to be processed. - */ -const ECERT_GENERATION_NO_RECORDS_AVAILABLE = - "ECERT_GENERATION_NO_RECORDS_AVAILABLE"; - /** * Error details: error id and block funding info * for each error code. @@ -101,37 +88,25 @@ export abstract class ECertFileHandler extends ESDCFileHandler { log.info( `Retrieving ${offeringIntensity} disbursements to generate the e-Cert file...`, ); - try { - const sequenceGroup = `${sequenceGroupPrefix}_${getISODateOnlyString( - new Date(), - )}`; - let uploadResult: ECertUploadResult; - await this.sequenceService.consumeNextSequence( - sequenceGroup, - async (nextSequenceNumber: number, entityManager: EntityManager) => { - uploadResult = await this.processECert( - nextSequenceNumber, - entityManager, - eCertIntegrationService, - offeringIntensity, - fileCode, - log, - ); - }, - ); - return uploadResult; - } catch (error: unknown) { - if ( - error instanceof CustomNamedError && - error.name === ECERT_GENERATION_NO_RECORDS_AVAILABLE - ) { - return { - generatedFile: "none", - uploadedRecords: 0, - }; - } - throw error; - } + + const sequenceGroup = `${sequenceGroupPrefix}_${getISODateOnlyString( + new Date(), + )}`; + let uploadResult: ECertUploadResult; + await this.sequenceService.consumeNextSequence( + sequenceGroup, + async (nextSequenceNumber: number, entityManager: EntityManager) => { + uploadResult = await this.processECert( + nextSequenceNumber, + entityManager, + eCertIntegrationService, + offeringIntensity, + fileCode, + log, + ); + }, + ); + return uploadResult; } /** @@ -144,7 +119,6 @@ export abstract class ECertFileHandler extends ESDCFileHandler { * @param fileCode file code applicable for Part-Time or Full-Time. * @param log cumulative process log. * @returns information of the uploaded e-Cert file. - * @throws CustomNamedError ECERT_GENERATION_NO_RECORDS_AVAILABLE */ private async processECert( sequenceNumber: number, @@ -154,71 +128,47 @@ export abstract class ECertFileHandler extends ESDCFileHandler { fileCode: string, log: ProcessSummary, ): Promise { - try { - const disbursements = - await this.eCertGenerationService.getReadyToSendDisbursements( - offeringIntensity, - entityManager, - ); - if (!disbursements.length) { - // Throws an exception to cancel the transaction and DB lock started by `consumeNextSequence`. - throw new CustomNamedError( - `There are no records available to generate an e-Cert file for ${offeringIntensity}`, - ECERT_GENERATION_NO_RECORDS_AVAILABLE, - ); - } - log.info( - `Found ${disbursements.length} ${offeringIntensity} disbursements schedules.`, - ); - const disbursementRecords = disbursements.map((disbursement) => - this.createECertRecord(disbursement), + const disbursements = + await this.eCertGenerationService.getReadyToSendDisbursements( + offeringIntensity, + entityManager, ); + log.info( + `Found ${disbursements.length} ${offeringIntensity} disbursements schedules.`, + ); + const disbursementRecords = disbursements.map((disbursement) => + this.createECertRecord(disbursement), + ); - log.info(`Creating ${offeringIntensity} e-Cert file content...`); - const fileContent = eCertIntegrationService.createRequestContent( - disbursementRecords, - sequenceNumber, - ); + log.info(`Creating ${offeringIntensity} e-Cert file content...`); + const fileContent = eCertIntegrationService.createRequestContent( + disbursementRecords, + sequenceNumber, + ); - // Create the request filename with the file path for the e-Cert File. - const fileInfo = this.createRequestFileName(fileCode, sequenceNumber); - log.info(`Uploading ${offeringIntensity} content...`); - await eCertIntegrationService.uploadContent( - fileContent, - fileInfo.filePath, + // Create the request filename with the file path for the e-Cert File. + const fileInfo = this.createRequestFileName(fileCode, sequenceNumber); + log.info(`Uploading ${offeringIntensity} content...`); + await eCertIntegrationService.uploadContent(fileContent, fileInfo.filePath); + // Mark all disbursements as sent. + const dateSent = new Date(); + const disbursementScheduleRepo = + entityManager.getRepository(DisbursementSchedule); + await processInParallel((disbursement) => { + return disbursementScheduleRepo.update( + { id: disbursement.id }, + { + dateSent, + disbursementScheduleStatus: DisbursementScheduleStatus.Sent, + updatedAt: dateSent, + modifier: this.systemUserService.systemUser, + }, ); - // Mark all disbursements as sent. - const dateSent = new Date(); - const disbursementScheduleRepo = - entityManager.getRepository(DisbursementSchedule); - await processInParallel((disbursement) => { - return disbursementScheduleRepo.update( - { id: disbursement.id }, - { - dateSent, - disbursementScheduleStatus: DisbursementScheduleStatus.Sent, - updatedAt: dateSent, - modifier: this.systemUserService.systemUser, - }, - ); - }, disbursements); - return { - generatedFile: fileInfo.filePath, - uploadedRecords: disbursementRecords.length, - }; - } catch (error: unknown) { - if ( - error instanceof CustomNamedError && - error.name !== ECERT_GENERATION_NO_RECORDS_AVAILABLE - ) { - log.error( - `Error while uploading content for ${offeringIntensity} e-Cert file: ${parseJSONError( - error, - )}`, - ); - } - throw error; - } + }, disbursements); + return { + generatedFile: fileInfo.filePath, + uploadedRecords: disbursementRecords.length, + }; } /**