Skip to content
Merged
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 @@ -11,6 +11,7 @@ import {
saveFakeApplicationDisbursements,
saveFakeCASSupplier,
saveFakeDesignationAgreementLocation,
saveFakeDisbursementReceiptsFromDisbursementSchedule,
saveFakeStudent,
} from "@sims/test-utils";
import {
Expand Down Expand Up @@ -46,7 +47,10 @@ import {
import { DataSource } from "typeorm";
import { createFakeEducationProgram } from "@sims/test-utils/factories/education-program";
import { createFakeSINValidation } from "@sims/test-utils/factories/sin-validation";
import { MinistryReportsFilterAPIInDTO } from "../../models/report.dto";
import {
MinistryReportNames,
MinistryReportsFilterAPIInDTO,
} from "../../models/report.dto";
import {
buildUnmetNeedReportData,
createApplicationsDataSetup,
Expand Down Expand Up @@ -1340,6 +1344,148 @@ describe("ReportAestController(e2e)-exportReport", () => {
});
});

it("Should generate the Disbursement report when a report generation request is made for full time offering intensity and date range.", async () => {
// Arrange
// Use a unique historical date to avoid conflicts with other tests.
const disburseDate = "2025-01-15";

const bcGrantBCAG = createFakeDisbursementValue(
DisbursementValueType.BCGrant,
"BCAG",
300,
);

const canadaLoanCSL = createFakeDisbursementValue(
DisbursementValueType.CanadaLoan,
"CSL",
1000,
);

const bcLoanBCSL = createFakeDisbursementValue(
DisbursementValueType.BCLoan,
"BCSL",
500,
);

const student = await saveFakeStudent(appDataSource);
// Full time application with disbursement values.
const application = await saveFakeApplicationDisbursements(
appDataSource,
{
firstDisbursementValues: [bcLoanBCSL, canadaLoanCSL, bcGrantBCAG],
student: student,
},
{
applicationStatus: ApplicationStatus.Completed,
offeringIntensity: OfferingIntensity.fullTime,
firstDisbursementInitialValues: {
coeStatus: COEStatus.completed,
disbursementScheduleStatus: DisbursementScheduleStatus.Sent,
disbursementDate: disburseDate,
},
},
);

// Part time application with disbursement values.
// This data is not used, its only created to ensure filtering works.
const partTimeApplication = await saveFakeApplicationDisbursements(
appDataSource,
{
firstDisbursementValues: [bcLoanBCSL, canadaLoanCSL, bcGrantBCAG],
},
{
applicationStatus: ApplicationStatus.Completed,
offeringIntensity: OfferingIntensity.partTime,
firstDisbursementInitialValues: {
coeStatus: COEStatus.completed,
disbursementScheduleStatus: DisbursementScheduleStatus.Sent,
disbursementDate: disburseDate,
},
},
);
// Create disbursement receipts for the part-time application using the utility method.
await saveFakeDisbursementReceiptsFromDisbursementSchedule(
db,
partTimeApplication.currentAssessment.disbursementSchedules[0],
);

// Create disbursement receipts for the report using the utility method.
const [firstDisbursement] =
application.currentAssessment.disbursementSchedules;
const { federal, provincial } =
await saveFakeDisbursementReceiptsFromDisbursementSchedule(
db,
firstDisbursement,
);

// Update receipt dates to match the unique test date to ensure proper filtering.
federal.disburseDate = disburseDate;
federal.batchRunDate = disburseDate;
federal.fileDate = disburseDate;
provincial.disburseDate = disburseDate;
provincial.batchRunDate = disburseDate;
provincial.fileDate = disburseDate;
await db.disbursementReceipt.save([federal, provincial]);

const payload = {
reportName: MinistryReportNames.Disbursements,
params: {
startDate: disburseDate,
endDate: getISODateOnlyString(addDays(1, disburseDate)),
offeringIntensity: {
"Full Time": true, // Filter to include only full time disbursements.
"Part Time": false,
},
},
};

// Mock the formio service dry run submission to return the payload.
const dryRunSubmissionMock = jest.fn().mockResolvedValue({
valid: true,
formName: FormNames.ExportFinancialReports,
data: { data: payload },
});
formService.dryRunSubmission = dryRunSubmissionMock;

const endpoint = "/aest/report";
const ministryUserToken = await getAESTToken(
AESTGroups.BusinessAdministrators,
);

// Act/Assert
await request(app.getHttpServer())
.post(endpoint)
.send(payload)
.auth(ministryUserToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.CREATED)
.then((response) => {
const fileContent = response.request.res["text"];
const parsedResult = parse(fileContent, {
header: true,
});
expect(parsedResult.data).toStrictEqual([
{
"Forecast Date": disburseDate,
"Date of Disbursement": disburseDate,
SIN: student.sinValidation.sin,
"Application Number": application.applicationNumber,
"Certificate Number": firstDisbursement.documentNumber.toString(),
"Funding Code": bcLoanBCSL.valueCode,
"Disbursement Amount": bcLoanBCSL.valueAmount.toFixed(2),
},
{
"Forecast Date": disburseDate,
"Date of Disbursement": disburseDate,
SIN: student.sinValidation.sin,
"Application Number": application.applicationNumber,
"Certificate Number": firstDisbursement.documentNumber.toString(),
"Funding Code": canadaLoanCSL.valueCode,
"Disbursement Amount": canadaLoanCSL.valueAmount.toFixed(2),
},
]);
});
});

it(
"Should generate CAS Supplier maintenance updates report with the student details of the given student" +
" when last name of the student is updated after the CAS supplier is set to be valid.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ enum InstitutionReportNames {
COERequests = "COE_Requests",
}

enum MinistryReportNames {
export enum MinistryReportNames {
ForecastDisbursements = "Disbursement_Forecast_Report",
Disbursements = "Disbursement_Report",
DisbursementsWithoutValidSupplier = "Disbursements_Without_Valid_Supplier_Report",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { getSQLFileData } from "../utilities/sqlLoader";

/**
* Update the disbursement reports to include the "Forecast Date" column.
*/
export class UpdateDisbursementReportForecastDate1767653404966 implements MigrationInterface {
/**
* Update the disbursement reports to include the "Forecast Date" column.
* @param queryRunner the query runner.
*/
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData("Update-disbursement-report-forecast-date.sql", "Reports"),
);
}

/**
* Rollback the disbursement reports update that included the "Forecast Date" column.
* @param queryRunner the query runner.
*/
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData(
"Rollback-update-disbursement-report-forecast-date.sql",
"Reports",
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
-- Rollback the disbursement report SQL to the previous version before the forecast date update.
UPDATE
sims.report_configs
SET
report_sql = $$ (
SELECT
to_char(dr.disburse_date, 'YYYY-MM-DD') AS "Date of Disbursement",
dr.student_sin AS "SIN",
app.application_number AS "Application Number",
ds.document_number AS "Certificate Number",
drv.grant_type AS "Funding Code",
drv.grant_amount AS "Disbursement Amount"
FROM
sims.disbursement_receipts dr
INNER JOIN sims.disbursement_receipt_values drv ON drv.disbursement_receipt_id = dr.id
INNER JOIN sims.disbursement_schedules ds ON ds.id = dr.disbursement_schedule_id
INNER JOIN sims.student_assessments sa ON sa.id = ds.student_assessment_id
INNER JOIN sims.applications app ON app.id = sa.application_id
INNER JOIN sims.education_programs_offerings epo ON epo.id = sa.offering_id
WHERE
epo.offering_intensity = ANY(:offeringIntensity)
AND dr.disburse_date BETWEEN :startDate
AND :endDate
UNION
ALL
SELECT
to_char(dr.disburse_date, 'YYYY-MM-DD') AS "Date of Disbursement",
dr.student_sin AS "SIN",
app.application_number AS "Application Number",
ds.document_number AS "Certificate Number",
dv.value_code AS "Funding Code",
dv.effective_amount AS "Disbursement Amount"
FROM
sims.disbursement_receipts dr
INNER JOIN sims.disbursement_receipt_values drv ON drv.disbursement_receipt_id = dr.id
INNER JOIN sims.disbursement_schedules ds ON ds.id = dr.disbursement_schedule_id
INNER JOIN sims.disbursement_values dv ON dv.disbursement_schedule_id = ds.id
INNER JOIN sims.student_assessments sa ON sa.id = ds.student_assessment_id
INNER JOIN sims.applications app ON app.id = sa.application_id
INNER JOIN sims.education_programs_offerings epo ON epo.id = sa.offering_id
WHERE
epo.offering_intensity = ANY(:offeringIntensity)
AND dr.funding_type <> 'FE'
AND drv.grant_type = 'BCSG'
AND dv.value_type = 'BC Grant'
AND dr.disburse_date BETWEEN :startDate
AND :endDate
UNION
ALL
SELECT
to_char(dr.disburse_date, 'YYYY-MM-DD') AS "Date of Disbursement",
dr.student_sin AS "SIN",
app.application_number AS "Application Number",
ds.document_number AS "Certificate Number",
CASE
WHEN dr.funding_type = 'BC' THEN 'BCSL'
WHEN dr.funding_type = 'FE' THEN 'CSL'
END AS "Funding Code",
dr.total_disbursed_amount AS "Disbursement Amount"
FROM
sims.disbursement_receipts dr
INNER JOIN sims.disbursement_schedules ds ON ds.id = dr.disbursement_schedule_id
INNER JOIN sims.student_assessments sa ON sa.id = ds.student_assessment_id
INNER JOIN sims.applications app ON app.id = sa.application_id
INNER JOIN sims.education_programs_offerings epo ON epo.id = sa.offering_id
WHERE
epo.offering_intensity = ANY(:offeringIntensity)
AND dr.disburse_date BETWEEN :startDate
AND :endDate
)
ORDER BY
"Date of Disbursement",
"Certificate Number" $$
WHERE
report_name = 'Disbursement_Report';
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
-- Update the Disbursements Report to include the "Forecast Date" column.
UPDATE
sims.report_configs
SET
report_sql = $$ (
SELECT
to_char(ds.disbursement_date, 'YYYY-MM-DD') AS "Forecast Date",
to_char(dr.disburse_date, 'YYYY-MM-DD') AS "Date of Disbursement",
dr.student_sin AS "SIN",
app.application_number AS "Application Number",
ds.document_number AS "Certificate Number",
drv.grant_type AS "Funding Code",
drv.grant_amount AS "Disbursement Amount"
FROM
sims.disbursement_receipts dr
INNER JOIN sims.disbursement_receipt_values drv ON drv.disbursement_receipt_id = dr.id
INNER JOIN sims.disbursement_schedules ds ON ds.id = dr.disbursement_schedule_id
INNER JOIN sims.student_assessments sa ON sa.id = ds.student_assessment_id
INNER JOIN sims.applications app ON app.id = sa.application_id
INNER JOIN sims.education_programs_offerings epo ON epo.id = sa.offering_id
WHERE
epo.offering_intensity = ANY(:offeringIntensity)
AND dr.disburse_date BETWEEN :startDate
AND :endDate
UNION
ALL
SELECT
to_char(ds.disbursement_date, 'YYYY-MM-DD') AS "Forecast Date",
to_char(dr.disburse_date, 'YYYY-MM-DD') AS "Date of Disbursement",
dr.student_sin AS "SIN",
app.application_number AS "Application Number",
ds.document_number AS "Certificate Number",
dv.value_code AS "Funding Code",
dv.effective_amount AS "Disbursement Amount"
FROM
sims.disbursement_receipts dr
INNER JOIN sims.disbursement_receipt_values drv ON drv.disbursement_receipt_id = dr.id
INNER JOIN sims.disbursement_schedules ds ON ds.id = dr.disbursement_schedule_id
INNER JOIN sims.disbursement_values dv ON dv.disbursement_schedule_id = ds.id
INNER JOIN sims.student_assessments sa ON sa.id = ds.student_assessment_id
INNER JOIN sims.applications app ON app.id = sa.application_id
INNER JOIN sims.education_programs_offerings epo ON epo.id = sa.offering_id
WHERE
epo.offering_intensity = ANY(:offeringIntensity)
AND dr.funding_type <> 'FE'
AND drv.grant_type = 'BCSG'
AND dv.value_type = 'BC Grant'
AND dr.disburse_date BETWEEN :startDate
AND :endDate
UNION
ALL
SELECT
to_char(ds.disbursement_date, 'YYYY-MM-DD') AS "Forecast Date",
to_char(dr.disburse_date, 'YYYY-MM-DD') AS "Date of Disbursement",
dr.student_sin AS "SIN",
app.application_number AS "Application Number",
ds.document_number AS "Certificate Number",
CASE
WHEN dr.funding_type = 'BC' THEN 'BCSL'
WHEN dr.funding_type = 'FE' THEN 'CSL'
END AS "Funding Code",
dr.total_disbursed_amount AS "Disbursement Amount"
FROM
sims.disbursement_receipts dr
INNER JOIN sims.disbursement_schedules ds ON ds.id = dr.disbursement_schedule_id
INNER JOIN sims.student_assessments sa ON sa.id = ds.student_assessment_id
INNER JOIN sims.applications app ON app.id = sa.application_id
INNER JOIN sims.education_programs_offerings epo ON epo.id = sa.offering_id
WHERE
epo.offering_intensity = ANY(:offeringIntensity)
AND dr.disburse_date BETWEEN :startDate
AND :endDate
)
ORDER BY
"Date of Disbursement",
"Certificate Number",
"Funding Code" $$
Copy link
Collaborator

Choose a reason for hiding this comment

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

question: Is this a new requirement? I don't see an AC for it.

Copy link
Collaborator Author

@tiago-graf tiago-graf Jan 6, 2026

Choose a reason for hiding this comment

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

This is not part of the AC
The current order by will cause the disbursement values to be returned in random order making it unpredictable in the tests.
The change however should not affect the reporting.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@tiago-graf I believe the strict equal/equal(one among the 2) does not enforce the order. If that is added for tests I would ask to remove it as we can handle the assertion.

Copy link
Collaborator

@weskubo-cgi weskubo-cgi Jan 7, 2026

Choose a reason for hiding this comment

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

I think we can also shift the disbursementDate by a day on federal or provincial to get a consistent date sort.

WHERE
report_name = 'Disbursement_Report';
Loading