Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Release branch v2.3.0 to main #4257

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,124 @@ describe("AssessmentStudentsController(e2e)-getAssessmentNOA", () => {
.expect(expectation);
});

it("Should exclude BC Total Grant from eligible amount calculation when getting NOA details", async () => {
// Arrange
const student = await saveFakeStudent(db.dataSource);
await mockUserLoginInfo(appModule, student);

// Create signed MSFAA
const msfaaNumber = createFakeMSFAANumber(
{ student },
{
msfaaState: MSFAAStates.Signed,
},
);
await db.msfaaNumber.save(msfaaNumber);

const application = await saveFakeApplicationDisbursements(
db.dataSource,
{
msfaaNumber,
student,
disbursementValues: [
// BC Total Grant (excluded from eligible amount)
createFakeDisbursementValue(
DisbursementValueType.BCTotalGrant,
"BCSG",
540,
),
// Canada Student Loans and Grants
createFakeDisbursementValue(
DisbursementValueType.CanadaLoan,
"CSLF",
0,
),
createFakeDisbursementValue(
DisbursementValueType.CanadaGrant,
"CSGP",
0,
),
createFakeDisbursementValue(
DisbursementValueType.CanadaGrant,
"CSGF",
2520,
),
// BC Grants
createFakeDisbursementValue(
DisbursementValueType.BCGrant,
"BCAG",
140,
),
createFakeDisbursementValue(
DisbursementValueType.BCGrant,
"SBSD",
400,
),
],
},
{
offeringIntensity: OfferingIntensity.fullTime,
},
);

const assessment = application.currentAssessment;
const [disbursement] = assessment.disbursementSchedules;

const endpoint = `/students/assessment/${assessment.id}/noa`;
const studentUserToken = await getStudentToken(
FakeStudentUsersTypes.FakeStudentUserType1,
);

const expectedNOADetails = {
applicationId: application.id,
applicationNumber: application.applicationNumber,
applicationStatus: application.applicationStatus,
assessment: assessment.assessmentData,
noaApprovalStatus: assessment.noaApprovalStatus,
applicationCurrentAssessmentId: application.currentAssessment.id,
fullName: getUserFullName(application.student.user),
programName: assessment.offering.educationProgram.name,
locationName: assessment.offering.institutionLocation.name,
offeringIntensity: OfferingIntensity.fullTime,
offeringStudyEndDate: getDateOnlyFullMonthFormat(
assessment.offering.studyEndDate,
),
offeringStudyStartDate: getDateOnlyFullMonthFormat(
assessment.offering.studyStartDate,
),
// Sum of CSGF(2520) + BCAG(140) + SBSD(400), excluding BCSG(540)
eligibleAmount: 3060,
disbursement: {
disbursement1COEStatus: disbursement.coeStatus,
disbursement1Date: getDateOnlyFullMonthFormat(
disbursement.disbursementDate,
),
disbursement1Id: disbursement.id,
disbursement1MSFAACancelledDate:
disbursement.msfaaNumber?.cancelledDate,
disbursement1MSFAADateSigned: disbursement.msfaaNumber?.dateSigned,
disbursement1MSFAAId: disbursement.msfaaNumber?.id,
disbursement1MSFAANumber: msfaaNumber.msfaaNumber,
disbursement1Status: disbursement.disbursementScheduleStatus,
disbursement1TuitionRemittance:
disbursement.tuitionRemittanceRequestedAmount,
disbursement1cslf: 0,
disbursement1csgp: 0,
disbursement1csgf: 2520,
disbursement1bcag: 140,
disbursement1sbsd: 400,
},
offeringName: assessment.offering.name,
};

// Act/Assert
await request(app.getHttpServer())
.get(endpoint)
.auth(studentUserToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.OK)
.expect(expectedNOADetails);
});

afterAll(async () => {
await app?.close();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ export class AssessmentControllerService {
.flatMap(
(disbursementSchedule) => disbursementSchedule.disbursementValues,
)
.filter(
(disbursementValue) =>
disbursementValue.valueType !== DisbursementValueType.BCTotalGrant,
)
.reduce(
(accumulator, disbursementValue) =>
accumulator + disbursementValue.valueAmount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from "./e-cert-utils";
import { SystemUsersService } from "@sims/services";
import * as faker from "faker";
import { ECERT_PART_TIME_SENT_FILE_SEQUENCE_GROUP } from "@sims/integrations/esdc-integration";

describe(
describeQueueProcessorRootTest(QueueNames.PartTimeECertIntegration),
Expand Down Expand Up @@ -97,7 +98,7 @@ describe(
);
// Reset sequence number to control the file name generated.
await db.sequenceControl.update(
{ sequenceName: Like("ECERT_PT_SENT_FILE_%") },
{ sequenceName: Like("ECERT_PT_SENT_FILE%") },
{ sequenceNumber: "0" },
);
});
Expand Down Expand Up @@ -384,6 +385,14 @@ describe(
},
},
);
const eCertLifetimeSequence = 100000;
await db.sequenceControl.upsert(
{
sequenceName: ECERT_PART_TIME_SENT_FILE_SEQUENCE_GROUP,
sequenceNumber: eCertLifetimeSequence.toString(),
},
["sequenceName"],
);
// Queued job.
const { job } = mockBullJob<void>();

Expand All @@ -401,7 +410,10 @@ describe(

expect(uploadedFile.fileLines).toHaveLength(3);
const [header, record, footer] = uploadedFile.fileLines;
const headerSequence = header.substring(58, 64);
// Validate header.
const expectedHeaderSequence = (eCertLifetimeSequence + 1).toString();
expect(headerSequence).toBe(expectedHeaderSequence);
expect(header).toContain("01BC NEW PT ENTITLEMENT");
// Validate footer.
// Record Type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "../../services";
import { SequenceControlService, SystemUsersService } from "@sims/services";
import { getISODateOnlyString, processInParallel } from "@sims/utilities";
import { EntityManager } from "typeorm";
import { DataSource, EntityManager } from "typeorm";
import { ESDCFileHandler } from "../esdc-file-handler";
import {
Award,
Expand All @@ -22,6 +22,7 @@ import { ConfigService, ESDCIntegrationConfig } from "@sims/utilities/config";
import { ECertGenerationService } from "@sims/integrations/services";
import { ECertResponseRecord } from "./e-cert-files/e-cert-response-record";
import * as path from "path";
import { CreateRequestFileNameResult } from "../models/esdc-integration.model";

/**
* Error details: error id and block funding info
Expand All @@ -41,6 +42,7 @@ type ECertFeedbackCodeMap = Record<string, ErrorDetails>;
export abstract class ECertFileHandler extends ESDCFileHandler {
esdcConfig: ESDCIntegrationConfig;
constructor(
private readonly dataSource: DataSource,
configService: ConfigService,
private readonly sequenceService: SequenceControlService,
private readonly eCertGenerationService: ECertGenerationService,
Expand Down Expand Up @@ -72,7 +74,7 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
* for the respective integration.
* @param offeringIntensity disbursement offering intensity.
* @param fileCode file code applicable for Part-Time or Full-Time.
* @param sequenceGroupPrefix sequence group prefix for Part-Time or Full-Time
* @param sequenceGroup sequence group for Part-Time or Full-Time
* file sequence generation.
* @param log cumulative process log.
* @returns result of the file upload with the file generated and the
Expand All @@ -82,30 +84,46 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
eCertIntegrationService: ECertIntegrationService,
offeringIntensity: OfferingIntensity,
fileCode: string,
sequenceGroupPrefix: string,
sequenceGroup: string,
log: ProcessSummary,
): Promise<ECertUploadResult> {
log.info(
`Retrieving ${offeringIntensity} disbursements to generate the e-Cert file...`,
);

const sequenceGroup = `${sequenceGroupPrefix}_${getISODateOnlyString(
const sequenceGroupFileName = `${sequenceGroup}_${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,
);
},
);
let fileInfo: CreateRequestFileNameResult;
await this.dataSource.transaction(async (transactionalEntityManager) => {
// Consume the next sequence number for the e-Cert filename
// and execute the creation of the request filename.
await this.sequenceService.consumeNextSequenceWithExistingEntityManager(
sequenceGroupFileName,
transactionalEntityManager,
async (nextSequenceNumber: number) => {
// Create the request filename with the file path for the e-Cert File.
fileInfo = this.createRequestFileName(fileCode, nextSequenceNumber);
},
);
// Consume the next sequence number for the e-Cert file header
// and execute the processECert process.
await this.sequenceService.consumeNextSequenceWithExistingEntityManager(
sequenceGroup,
transactionalEntityManager,
async (nextSequenceNumber: number, entityManager: EntityManager) => {
uploadResult = await this.processECert(
nextSequenceNumber,
entityManager,
eCertIntegrationService,
offeringIntensity,
fileInfo,
log,
);
},
);
});
return uploadResult;
}

Expand All @@ -116,7 +134,7 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
* @param eCertIntegrationService Full-Time/Part-Time integration responsible
* for the respective integration.
* @param offeringIntensity disbursement offering intensity.
* @param fileCode file code applicable for Part-Time or Full-Time.
* @param fileInfo e-Cert file information.
* @param log cumulative process log.
* @returns information of the uploaded e-Cert file.
*/
Expand All @@ -125,7 +143,7 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
entityManager: EntityManager,
eCertIntegrationService: ECertIntegrationService,
offeringIntensity: OfferingIntensity,
fileCode: string,
fileInfo: CreateRequestFileNameResult,
log: ProcessSummary,
): Promise<ECertUploadResult> {
const disbursements =
Expand All @@ -146,8 +164,6 @@ export abstract class ECertFileHandler extends ESDCFileHandler {
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);
// Mark all disbursements as sent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import { ECertGenerationService } from "@sims/integrations/services";
import { ECertFileHandler } from "./e-cert-file-handler";
import { ECertFullTimeIntegrationService } from "./e-cert-full-time-integration/e-cert-full-time.integration.service";
import { ProcessSummary } from "@sims/utilities/logger";
import { DataSource } from "typeorm";

const ECERT_FULL_TIME_SENT_FILE_SEQUENCE_GROUP = "ECERT_FT_SENT_FILE";

@Injectable()
export class FullTimeECertFileHandler extends ECertFileHandler {
esdcConfig: ESDCIntegrationConfig;
constructor(
dataSource: DataSource,
configService: ConfigService,
sequenceService: SequenceControlService,
eCertGenerationService: ECertGenerationService,
Expand All @@ -31,6 +33,7 @@ export class FullTimeECertFileHandler extends ECertFileHandler {
private readonly eCertIntegrationService: ECertFullTimeIntegrationService,
) {
super(
dataSource,
configService,
sequenceService,
eCertGenerationService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import { ECertGenerationService } from "@sims/integrations/services";
import { ECertFileHandler } from "./e-cert-file-handler";
import { ECertPartTimeIntegrationService } from "./e-cert-part-time-integration/e-cert-part-time.integration.service";
import { ProcessSummary } from "@sims/utilities/logger";
import { DataSource } from "typeorm";

const ECERT_PART_TIME_SENT_FILE_SEQUENCE_GROUP = "ECERT_PT_SENT_FILE";
export const ECERT_PART_TIME_SENT_FILE_SEQUENCE_GROUP = "ECERT_PT_SENT_FILE";

@Injectable()
export class PartTimeECertFileHandler extends ECertFileHandler {
esdcConfig: ESDCIntegrationConfig;
constructor(
dataSource: DataSource,
configService: ConfigService,
sequenceService: SequenceControlService,
eCertGenerationService: ECertGenerationService,
Expand All @@ -31,6 +33,7 @@ export class PartTimeECertFileHandler extends ECertFileHandler {
private readonly eCertIntegrationService: ECertPartTimeIntegrationService,
) {
super(
dataSource,
configService,
sequenceService,
eCertGenerationService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ export class SFASIndividualService {
): Promise<SFASIndividual> {
const individual = await this.sfasIndividualRepo
.createQueryBuilder("individual")
.select(["individual.id", "individual.pdStatus"])
.select([
"individual.id",
"individual.pdStatus",
"individual.ppdStatus",
"individual.ppdStatusDate",
])
.where("lower(individual.lastName) = :lastName", {
lastName: lastName.toLowerCase(),
})
Expand Down
Loading