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

#4015 - FT program year total contribution limits #4154

Merged
merged 27 commits into from
Dec 23, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
DisbursementValueType,
OfferingIntensity,
StudentAssessmentStatus,
WorkflowData,
} from "@sims/sims-db";
import { addDays, getISODateOnlyString } from "@sims/utilities";
import { createFakeVerifyAssessmentCalculationOrderPayload } from "./verify-assessment-calculation-order-factory";
Expand Down Expand Up @@ -283,7 +284,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
});
});

it("Should sum the grants from past applications with different offering intensities and awards from SFAS and SFAS part time applications data when the applications are for the same student and program year.", async () => {
it("Should sum the grants from past applications with different offering intensities and awards from SFAS and SFAS part-time applications data when the applications are for the same student and program year.", async () => {
// Arrange

// Create the student and program year to be shared across the applications.
Expand Down Expand Up @@ -395,13 +396,13 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
);
const firstAssessmentDate =
currentApplication.currentAssessment.assessmentDate;
// The start date for the first SFAS and SFAS part time application record is set to the date before the first assessment date of the current application.
// The start date for the first SFAS and SFAS part-time application record is set to the date before the first assessment date of the current application.
const firstLegacyApplicationStartDate = faker.date.between(
programYear.startDate,
addDays(-1, firstAssessmentDate),
);
const firstLegacyApplicationEndDate = addDays(30, firstAssessmentDate);
// The start date for the second SFAS and SFAS part time application record is set to the date after the end date of the first SFAS application.
// The start date for the second SFAS and SFAS part-time application record is set to the date after the end date of the first SFAS application.
const secondLegacyApplicationStartDate = faker.date.between(
firstLegacyApplicationEndDate,
addDays(10, firstLegacyApplicationEndDate),
Expand Down Expand Up @@ -466,7 +467,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
firstFakeSFASApplication,
secondFakeSFASApplication,
]);
// First SFAS part time application with the start date before the first assessment date of the current application.
// First SFAS part-time application with the start date before the first assessment date of the current application.
const firstFakeSFASPartTimeApplication = createFakeSFASPartTimeApplication(
{ individual: sfasIndividual },
{
Expand All @@ -481,7 +482,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
},
},
);
// Second SFAS part time application with the start date after the end date of the first SFAS part time application.
// Second SFAS part-time application with the start date after the end date of the first SFAS part-time application.
const secondFakeSFASPartTimeApplication = createFakeSFASPartTimeApplication(
{ individual: sfasIndividual },
{
Expand Down Expand Up @@ -510,7 +511,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
expect(FakeWorkerJobResult.getResultType(result)).toBe(
MockedZeebeJobResult.Complete,
);
// The calculation will only take SFAS and SFAS part time application data where the start date is the date before the first assessment date of the current application.
// The calculation will only take SFAS and SFAS part-time application data where the start date is the date before the first assessment date of the current application.
expect(FakeWorkerJobResult.getOutputVariables(result)).toStrictEqual({
isReadyForCalculation: true,
latestCSLPBalance: 0,
Expand Down Expand Up @@ -666,7 +667,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
},
);

it("Should sum the awards from SFAS and SFAS part time applications data when there is no past SIMS application for the same student and program year.", async () => {
it("Should sum the awards from SFAS and SFAS part-time applications data when there is no past SIMS application for the same student and program year.", async () => {
// Arrange

// Create the student to be shared across the applications.
Expand All @@ -690,13 +691,13 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
);
const firstAssessmentDate =
currentApplication.currentAssessment.assessmentDate;
// The start date for the first SFAS and SFAS part time application record is set to the date before the first assessment date of the current application.
// The start date for the first SFAS and SFAS part-time application record is set to the date before the first assessment date of the current application.
const firstLegacyApplicationStartDate = faker.date.between(
programYear.startDate,
addDays(-1, firstAssessmentDate),
);
const firstLegacyApplicationEndDate = addDays(30, firstAssessmentDate);
// The start date for the second SFAS and SFAS part time application record is set to the date after the end date of the first SFAS application.
// The start date for the second SFAS and SFAS part-time application record is set to the date after the end date of the first SFAS application.
const secondLegacyApplicationStartDate = faker.date.between(
firstLegacyApplicationEndDate,
addDays(10, firstLegacyApplicationEndDate),
Expand Down Expand Up @@ -762,7 +763,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
firstFakeSFASApplication,
secondFakeSFASApplication,
]);
// First SFAS part time application with the start date before the first assessment date of the current application.
// First SFAS part-time application with the start date before the first assessment date of the current application.
const firstFakeSFASPartTimeApplication = createFakeSFASPartTimeApplication(
{ individual: sfasIndividual },
{
Expand All @@ -777,7 +778,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
},
},
);
// Second SFAS part time application with the start date after the end date of the first SFAS part time application.
// Second SFAS part-time application with the start date after the end date of the first SFAS part-time application.
const secondFakeSFASPartTimeApplication = createFakeSFASPartTimeApplication(
{ individual: sfasIndividual },
{
Expand Down Expand Up @@ -806,7 +807,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
expect(FakeWorkerJobResult.getResultType(result)).toBe(
MockedZeebeJobResult.Complete,
);
// The calculation will only take SFAS and SFAS part time application data where the start date is the date before the first assessment date of the current application.
// The calculation will only take SFAS and SFAS part-time application data where the start date is the date before the first assessment date of the current application.
expect(FakeWorkerJobResult.getOutputVariables(result)).toStrictEqual({
isReadyForCalculation: true,
latestCSLPBalance: 0,
Expand All @@ -824,7 +825,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
});
});

it("Should not return any program year total awards or grants from awards from SFAS and SFAS part time applications when there are no SIMS applications in the past and SFAS and SFAS part time applications for the same student and program year.", async () => {
it("Should not return any program year total awards or grants from awards from SFAS and SFAS part-time applications when there are no SIMS applications in the past and SFAS and SFAS part-time applications for the same student and program year.", async () => {
// Arrange

// Create the student to be shared across the applications.
Expand Down Expand Up @@ -854,7 +855,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
expect(FakeWorkerJobResult.getResultType(result)).toBe(
MockedZeebeJobResult.Complete,
);
// The result will not have data from SFAS or SFAS part time application data.
// The result will not have data from SFAS or SFAS part-time application data.
expect(FakeWorkerJobResult.getOutputVariables(result)).toStrictEqual({
isReadyForCalculation: true,
latestCSLPBalance: 0,
Expand Down Expand Up @@ -918,7 +919,7 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
expect(FakeWorkerJobResult.getResultType(result)).toBe(
MockedZeebeJobResult.Complete,
);
// The result will not have data from SFAS or SFAS part time application data.
// The result will not have data from SFAS or SFAS part-time application data.
expect(FakeWorkerJobResult.getOutputVariables(result)).toStrictEqual({
isReadyForCalculation: true,
latestCSLPBalance: 1000,
Expand Down Expand Up @@ -969,9 +970,133 @@ describe("AssessmentController(e2e)-verifyAssessmentCalculationOrder", () => {
expect(FakeWorkerJobResult.getResultType(result)).toBe(
MockedZeebeJobResult.Complete,
);
// The result will not have data from SFAS or SFAS part time application data.
// The result will not have data from SFAS or SFAS part-time application data.
expect(FakeWorkerJobResult.getOutputVariables(result)).toStrictEqual({
isReadyForCalculation: true,
// Student contribution total.
programYearTotalFederalFSC: 0,
programYearTotalProvincialFSC: 0,
programYearTotalScholarshipsBursaries: 0,
programYearTotalSpouseContributionWeeks: 0,
Comment on lines +977 to +980
Copy link
Collaborator

@andrewsignori-aot andrewsignori-aot Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question for a later comment, should we return 0 when values are not defined?
If the SUM results in 0 I do not see a problem, but having 0 as a fallback for an undefined value, do we need that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are not doing any calculation still in the workflow, i thought it would be better to return 0 than null, as the variable name states "programYearTotal" either it can be 0 or some numeric value.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is the variable programYearTotal* will not be even there if not present.

});
});

it("Should sum the contributions from past applications with full-time offering intensities when the applications are for the same student and program year and the student submits a full-time application.", async () => {
// Arrange

// Create the student and program year to be shared across the applications.
const student = await saveFakeStudent(db.dataSource);
// Get the program year for the start date.
const programYear = await db.programYear.findOne({ where: { id: 2 } });

// Past full-time application with federal and provincial loans and grants.
// Loans will be ignored.
await saveFakeApplicationDisbursements(
db.dataSource,
{
student,
firstDisbursementValues: [
createFakeDisbursementValue(
DisbursementValueType.CanadaLoan,
"CSLF",
1,
),
],
},
{
offeringIntensity: OfferingIntensity.fullTime,
applicationStatus: ApplicationStatus.Completed,
currentAssessmentInitialValues: {
studentAssessmentStatus: StudentAssessmentStatus.Completed,
assessmentDate: addDays(10, programYear.startDate),
workflowData: {
calculatedData: {
studentSpouseContributionWeeks: 1000,
exemptScholarshipsBursaries: 2000,
totalFederalFSC: 3000,
totalProvincialFSC: 4000,
},
} as WorkflowData,
},
},
);
// Past full-time application with federal and provincial loans and grants.
// Loans will be ignored.
await saveFakeApplicationDisbursements(
db.dataSource,
{
student,
firstDisbursementValues: [
createFakeDisbursementValue(
DisbursementValueType.CanadaLoan,
"CSLF",
8,
),
],
},
{
offeringIntensity: OfferingIntensity.fullTime,
applicationStatus: ApplicationStatus.Completed,
currentAssessmentInitialValues: {
studentAssessmentStatus: StudentAssessmentStatus.Completed,
assessmentDate: addDays(20, programYear.startDate),
workflowData: {
calculatedData: {
studentSpouseContributionWeeks: 1000,
exemptScholarshipsBursaries: 2000,
totalFederalFSC: 3000,
totalProvincialFSC: 4000,
},
} as WorkflowData,
},
},
);
// Current application having the first assessment already processed.
const currentApplication = await saveFakeApplicationDisbursements(
db.dataSource,
{ student },
{
offeringIntensity: OfferingIntensity.fullTime,
applicationStatus: ApplicationStatus.InProgress,
currentAssessmentInitialValues: {
studentAssessmentStatus: StudentAssessmentStatus.Completed,
assessmentDate: addDays(30, programYear.startDate),
},
},
);
const secondAssessment = createFakeStudentAssessment(
{
auditUser: currentApplication.student.user,
application: currentApplication,
offering: currentApplication.currentAssessment.offering,
},
{
initialValue: {
studentAssessmentStatus: StudentAssessmentStatus.InProgress,
},
},
);
currentApplication.currentAssessment = secondAssessment;
await db.application.save(currentApplication);

// Act
const result = await assessmentController.verifyAssessmentCalculationOrder(
createFakeVerifyAssessmentCalculationOrderPayload(
currentApplication.currentAssessment.id,
),
);
// Assert
expect(FakeWorkerJobResult.getResultType(result)).toBe(
MockedZeebeJobResult.Complete,
);
// The calculation will only take full-time and part-time application data where the start date is the date before the first assessment date of the current application.
expect(FakeWorkerJobResult.getOutputVariables(result)).toStrictEqual({
isReadyForCalculation: true,
// Student contribution total.
programYearTotalFederalFSC: 6000,
programYearTotalProvincialFSC: 8000,
programYearTotalScholarshipsBursaries: 4000,
programYearTotalSpouseContributionWeeks: 2000,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { MaxJobsToActivate } from "../../types";
import {
AssessmentSequentialProcessingService,
AwardTotal,
FTProgramYearContributionTotal,
SystemUsersService,
WorkflowClientService,
} from "@sims/services";
Expand Down Expand Up @@ -372,6 +373,7 @@ export class AssessmentController {
const assessmentId = job.variables.assessmentId;
const studentId = assessment.application.student.id;
const programYearId = assessment.application.programYear.id;
const offeringIntensity = assessment.offering.offeringIntensity;
jobLogger.log(
`Verifying the assessment calculation order for processing assessment id ${assessmentId} student id ${studentId} ` +
`program year id ${programYearId}.`,
Expand Down Expand Up @@ -420,15 +422,19 @@ export class AssessmentController {
this.studentAssessmentService.saveAssessmentCalculationStartDate(
assessmentId,
);
const getProgramYearTotalAwards =
this.assessmentSequentialProcessingService.getProgramYearPreviousAwardsTotals(
const programYearTotals =
await this.assessmentSequentialProcessingService.getProgramYearTotals(
assessmentId,
{ alternativeReferenceDate: new Date() },
{
alternativeReferenceDate: new Date(),
offeringIntensity: offeringIntensity,
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
},
);
// Updates the calculation start date and get the program year totals in parallel.
const [, programYearTotalAwards] = await Promise.all([
// Updates the calculation start date and get the program year totals and for
// full-time the contribution program year totals in parallel.
const [programYearAwardsContributionTotal] = await Promise.all([
programYearTotals,
saveAssessmentCalculationStartDate,
getProgramYearTotalAwards,
]);
if (
assessment.offering.offeringIntensity === OfferingIntensity.partTime
Expand All @@ -442,7 +448,11 @@ export class AssessmentController {
jobLogger.log(
`The assessment calculation order has been verified and the assessment id ${assessmentId} is ready to be processed.`,
);
this.createOutputForProgramYearTotals(programYearTotalAwards, result);
this.createOutputForProgramYearTotals(
programYearAwardsContributionTotal.awardTotal,
programYearAwardsContributionTotal.ftProgramYearContributionTotal,
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
result,
);
result.isReadyForCalculation = true;
return job.complete(result);
}
Expand All @@ -469,6 +479,7 @@ export class AssessmentController {
*/
private createOutputForProgramYearTotals(
programYearTotalAwards: AwardTotal[],
ftProgramYearContributionTotal: FTProgramYearContributionTotal[],
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
output: VerifyAssessmentCalculationOrderJobOutDTO,
): void {
// Create the dynamic variables to be outputted.
Expand All @@ -482,6 +493,12 @@ export class AssessmentController {
? output[outputName] + award.total
: award.total;
});
ftProgramYearContributionTotal?.forEach((ftContributionTotal) => {
const outputName = `programYearTotal${ftContributionTotal.contribution}`;
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
output[outputName] = output[outputName]
? output[outputName] + ftContributionTotal.total
: ftContributionTotal.total;
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OfferingIntensity } from "@sims/sims-db";
import { OfferingIntensity, StudentAssessment } from "@sims/sims-db";

/**
* Application information part of a sequence of ordered applications.
Expand Down Expand Up @@ -124,3 +124,28 @@ export interface AwardTotal {
valueCode: string;
total: number;
}

/**
* Program year totals with award code (e.g. CSPT, CSGD, CSGP, SBSD, BCAG) and its totals promise
* and for full-time program year contribution (e.g. ScholarshipsBursaries, SpouseContributionWeeks, FederalFSC, ProvincialFSC) and its totals promise.
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
*/
export interface ProgramYearTotal {
awardTotal: AwardTotal[];
ftProgramYearContributionTotal?: FTProgramYearContributionTotal[];
}

andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
/**
* Full-time program year contribution and its totals.
*/
export interface FTProgramYearContributionTotal {
contribution: string;
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
total: number;
}

/**
* Sequenced applications and current assessment.
*/
export interface SequencedApplicationsWithAssessment {
sequencedApplications: SequencedApplications;
andrewsignori-aot marked this conversation as resolved.
Show resolved Hide resolved
currentAssessment: StudentAssessment;
}
Loading
Loading