From 61015a79a5ea9c471367f927a0c301afdb67f7a3 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Wed, 4 Dec 2024 10:42:10 -0700 Subject: [PATCH 01/14] initial commit --- .../backend/apps/api/src/constants/error-code.constants.ts | 1 + .../services/src/cas-supplier/cas-supplier.shared.service.ts | 2 +- .../backend/libs/sims-db/src/entities/cas-supplier.model.ts | 4 ++-- sources/packages/web/src/constants/report-constants.ts | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sources/packages/backend/apps/api/src/constants/error-code.constants.ts b/sources/packages/backend/apps/api/src/constants/error-code.constants.ts index 48b7dad85d..b8ac697319 100644 --- a/sources/packages/backend/apps/api/src/constants/error-code.constants.ts +++ b/sources/packages/backend/apps/api/src/constants/error-code.constants.ts @@ -239,6 +239,7 @@ export const APPLICATION_RESTRICTION_BYPASS_NOT_FOUND = */ export const APPLICATION_RESTRICTION_BYPASS_IS_NOT_ACTIVE = "APPLICATION_RESTRICTION_BYPASS_IS_NOT_ACTIVE"; + /** * Student not found error code. */ diff --git a/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts b/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts index b3c2e0df35..d3ee5766fe 100644 --- a/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts +++ b/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts @@ -12,7 +12,7 @@ export class CASSupplierSharedService { * @returns student profile snapshot. */ getStudentProfileSnapshot( - firstName: string, + firstName: string = null, lastName: string, sin: string, addressInfo: AddressInfo, diff --git a/sources/packages/backend/libs/sims-db/src/entities/cas-supplier.model.ts b/sources/packages/backend/libs/sims-db/src/entities/cas-supplier.model.ts index 714b2e684d..bd3e72e316 100644 --- a/sources/packages/backend/libs/sims-db/src/entities/cas-supplier.model.ts +++ b/sources/packages/backend/libs/sims-db/src/entities/cas-supplier.model.ts @@ -164,12 +164,12 @@ export interface SupplierAddress { * Student profile snapshot information. */ export interface StudentProfileSnapshot { - firstName: string; + firstName?: string; lastName: string; sin: string; addressLine1: string; city: string; - province: string; + province?: string; postalCode: string; country: string; } diff --git a/sources/packages/web/src/constants/report-constants.ts b/sources/packages/web/src/constants/report-constants.ts index 19f9bbcff5..eb5442889a 100644 --- a/sources/packages/web/src/constants/report-constants.ts +++ b/sources/packages/web/src/constants/report-constants.ts @@ -33,4 +33,8 @@ export const MINISTRY_REPORTS: OptionItemAPIOutDTO[] = [ description: "Student Unmet Need", id: "Ministry_Student_Unmet_Need_Report", }, + { + description: "CAS Supplier Maintenance Updates", + id: "CAS_Supplier_Maintenance_Updates_Report", + }, ]; From d920c333b6df6da0d34d51cfb1ee227251aaeac2 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Wed, 4 Dec 2024 14:08:41 -0700 Subject: [PATCH 02/14] Added New Report CAS Supplier Maintenance updates --- .../report/models/report.dto.ts | 1 + ...0941443-AddCASSupplierMaintenanceReport.ts | 24 +++ ...as-supplier-maintenance-updates-report.sql | 199 ++++++++++++++++++ ...as-supplier-maintenance-updates-report.sql | 4 + 4 files changed, 228 insertions(+) create mode 100644 sources/packages/backend/apps/db-migrations/src/migrations/1733340941443-AddCASSupplierMaintenanceReport.ts create mode 100644 sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql create mode 100644 sources/packages/backend/apps/db-migrations/src/sql/Reports/Rollback-create-cas-supplier-maintenance-updates-report.sql diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/models/report.dto.ts b/sources/packages/backend/apps/api/src/route-controllers/report/models/report.dto.ts index 679034231f..8a672ddd92 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/models/report.dto.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/models/report.dto.ts @@ -24,6 +24,7 @@ enum MinistryReportNames { InstitutionDesignation = "Institution_Designation_Report", StudentUnmetNeed = "Ministry_Student_Unmet_Need_Report", ProgramAndOfferingStatus = "Program_And_Offering_Status_Report", + CASSupplierMaintenanceUpdates = "CAS_Supplier_Maintenance_Updates_Report", } /** diff --git a/sources/packages/backend/apps/db-migrations/src/migrations/1733340941443-AddCASSupplierMaintenanceReport.ts b/sources/packages/backend/apps/db-migrations/src/migrations/1733340941443-AddCASSupplierMaintenanceReport.ts new file mode 100644 index 0000000000..2b66249151 --- /dev/null +++ b/sources/packages/backend/apps/db-migrations/src/migrations/1733340941443-AddCASSupplierMaintenanceReport.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; +import { getSQLFileData } from "../utilities/sqlLoader"; + +export class AddCASSupplierMaintenanceReport1733340941443 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + getSQLFileData( + "Create-cas-supplier-maintenance-updates-report.sql", + "Reports", + ), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + getSQLFileData( + "Rollback-create-cas-supplier-maintenance-updates-report.sql", + "Reports", + ), + ); + } +} diff --git a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql new file mode 100644 index 0000000000..5bc3211ba1 --- /dev/null +++ b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql @@ -0,0 +1,199 @@ +INSERT INTO + sims.report_configs (report_name, report_sql) +VALUES + ( + 'CAS_Supplier_Maintenance_Updates_Report', + 'WITH cas_supplier_report AS ( + SELECT + student_user.first_name AS student_first_name, + student_user.last_name AS student_last_name, + student_user.identity_provider_type AS student_profile_type, + student.updated_at AS student_updated_date, + student.disability_status AS student_disability_status, + sin_validation.sin AS student_sin, + student.contact_info -> ''address'' ->> ''addressLine1'' AS student_address_line_1, + student.contact_info -> ''address'' ->> ''city'' AS student_city, + student.contact_info -> ''address'' ->> ''provinceState'' AS student_province, + student.contact_info -> ''address'' ->> ''postalCode'' AS student_postal_code, + student.contact_info -> ''address'' ->> ''country'' AS student_country, + cas_supplier.supplier_number AS cas_supplier, + cas_supplier.supplier_protected AS cas_supplier_protected, + cas_supplier.supplier_address ->> ''supplierSiteCode'' AS cas_site, + cas_supplier.supplier_address ->> ''siteProtected'' AS cas_site_protected, + cas_supplier.supplier_status_updated_on AS cas_supplier_verified_date, + cas_supplier.student_profile_snapshot ->> ''firstName'' AS cas_snapshot_first_name, + cas_supplier.student_profile_snapshot ->> ''lastName'' AS cas_snapshot_last_name, + cas_supplier.student_profile_snapshot ->> ''sin'' AS cas_snapshot_sin, + cas_supplier.student_profile_snapshot ->> ''addressLine1'' AS cas_snapshot_address_line_1, + cas_supplier.student_profile_snapshot ->> ''city'' AS cas_snapshot_city, + cas_supplier.student_profile_snapshot ->> ''province'' AS cas_snapshot_province, + cas_supplier.student_profile_snapshot ->> ''postalCode'' AS cas_snapshot_postal_code, + cas_supplier.student_profile_snapshot ->> ''country'' AS cas_snapshot_country + FROM + sims.students student + INNER JOIN sims.cas_suppliers cas_supplier on student.cas_supplier_id = cas_supplier.id + INNER JOIN sims.users student_user on student.user_id = student_user.id + INNER JOIN sims.sin_validations sin_validation on student.sin_validation_id = sin_validation.id + WHERE + cas_supplier.is_valid = true + AND ( + ( + ( + cas_supplier.student_profile_snapshot ->> ''firstName'' + ) IS DISTINCT + FROM + student_user.first_name + ) + OR ( + ( + cas_supplier.student_profile_snapshot ->> ''lastName'' + ) IS DISTINCT + FROM + student_user.last_name + ) + OR ( + ( + cas_supplier.student_profile_snapshot ->> ''sin'' + ) IS DISTINCT + FROM + sin_validation.sin + ) + OR ( + ( + cas_supplier.student_profile_snapshot ->> ''addressLine1'' + ) IS DISTINCT + FROM + ( + student.contact_info -> ''address'' ->> ''addressLine1'' + ) + ) + OR ( + ( + cas_supplier.student_profile_snapshot ->> ''city'' + ) IS DISTINCT + FROM + ( + student.contact_info -> ''address'' ->> ''city'' + ) + ) + OR ( + ( + cas_supplier.student_profile_snapshot ->> ''province'' + ) IS DISTINCT + FROM + ( + student.contact_info -> ''address'' ->> ''provinceState'' + ) + ) + OR ( + ( + cas_supplier.student_profile_snapshot ->> ''postalCode'' + ) IS DISTINCT + FROM + ( + student.contact_info -> ''address'' ->> ''postalCode'' + ) + ) + OR ( + ( + cas_supplier.student_profile_snapshot ->> ''country'' + ) IS DISTINCT + FROM + ( + student.contact_info -> ''address'' ->> ''country'' + ) + ) + ) + ) + SELECT + cas_supplier_report.student_first_name AS "Given Names", + cas_supplier_report.student_last_name AS "Last Name", + cas_supplier_report.student_sin AS "SIN", + cas_supplier_report.student_address_line_1 AS "Address Line 1", + cas_supplier_report.student_city AS "City", + cas_supplier_report.student_province AS "Province", + cas_supplier_report.student_postal_code AS "Postal Code", + cas_supplier_report.student_country AS "Country", + cas_supplier_report.student_disability_status AS "Disability Status", + cas_supplier_report.student_profile_type AS "Profile Type", + TO_CHAR( + cas_supplier_report.student_updated_date, + ''YYYY - MM - DD'' + ) AS "Student Updated Date", + cas_supplier_report.cas_supplier AS "Supplier", + cas_supplier_report.cas_site AS "Site", + cas_supplier_report.cas_supplier_protected AS "Protected Supplier", + cas_supplier_report.cas_site_protected AS "Protected Site", + TO_CHAR( + cas_supplier_report.cas_supplier_verified_date, + ''YYYY - MM - DD'' + ) AS "Supplier Verified Date", + CASE + WHEN ( + cas_supplier_report.student_first_name IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_first_name + ) THEN ''Yes'' + ELSE ''No'' + END "First Name Updated", + CASE + WHEN ( + cas_supplier_report.student_last_name IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_last_name + ) THEN ''Yes'' + ELSE ''No'' + END "Last Name Updated", + CASE + WHEN ( + cas_supplier_report.student_sin IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_sin + ) THEN ''Yes'' + ELSE ''No'' + END "SIN Updated", + CASE + WHEN ( + cas_supplier_report.student_address_line_1 IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_address_line_1 + ) THEN ''Yes'' + ELSE ''No'' + END "Address Line 1 Updated", + CASE + WHEN ( + cas_supplier_report.student_city IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_city + ) THEN ''Yes'' + ELSE ''No'' + END "City Updated", + CASE + WHEN ( + cas_supplier_report.student_province IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_province + ) THEN ''Yes'' + ELSE ''No'' + END "Province Updated", + CASE + WHEN ( + cas_supplier_report.student_postal_code IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_postal_code + ) THEN ''Yes'' + ELSE ''No'' + END "Postal Code Updated", + CASE + WHEN ( + cas_supplier_report.student_country IS DISTINCT + FROM + cas_supplier_report.cas_snapshot_country + ) THEN ''Yes'' + ELSE ''No'' + END "Country Updated" + FROM + cas_supplier_report + ORDER BY + student_updated_date;' + ); \ No newline at end of file diff --git a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Rollback-create-cas-supplier-maintenance-updates-report.sql b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Rollback-create-cas-supplier-maintenance-updates-report.sql new file mode 100644 index 0000000000..64d5f5bb01 --- /dev/null +++ b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Rollback-create-cas-supplier-maintenance-updates-report.sql @@ -0,0 +1,4 @@ +DELETE from + sims.report_configs +WHERE + report_name = 'CAS_Supplier_Maintenance_Updates_Report'; \ No newline at end of file From 62135c0b1a87e72536bf11ca71360ead5554d6c5 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Fri, 6 Dec 2024 09:20:08 -0700 Subject: [PATCH 03/14] updated the migration --- ...as-supplier-maintenance-updates-report.sql | 169 ++++-------------- 1 file changed, 36 insertions(+), 133 deletions(-) diff --git a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql index 5bc3211ba1..0e735d13a9 100644 --- a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql +++ b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql @@ -5,7 +5,7 @@ VALUES 'CAS_Supplier_Maintenance_Updates_Report', 'WITH cas_supplier_report AS ( SELECT - student_user.first_name AS student_first_name, + COALESCE(student_user.first_name, '''') AS student_first_name, student_user.last_name AS student_last_name, student_user.identity_provider_type AS student_profile_type, student.updated_at AS student_updated_date, @@ -13,7 +13,10 @@ VALUES sin_validation.sin AS student_sin, student.contact_info -> ''address'' ->> ''addressLine1'' AS student_address_line_1, student.contact_info -> ''address'' ->> ''city'' AS student_city, - student.contact_info -> ''address'' ->> ''provinceState'' AS student_province, + COALESCE( + student.contact_info -> ''address'' ->> ''provinceState'', + '''' + ) AS student_province, student.contact_info -> ''address'' ->> ''postalCode'' AS student_postal_code, student.contact_info -> ''address'' ->> ''country'' AS student_country, cas_supplier.supplier_number AS cas_supplier, @@ -21,12 +24,18 @@ VALUES cas_supplier.supplier_address ->> ''supplierSiteCode'' AS cas_site, cas_supplier.supplier_address ->> ''siteProtected'' AS cas_site_protected, cas_supplier.supplier_status_updated_on AS cas_supplier_verified_date, - cas_supplier.student_profile_snapshot ->> ''firstName'' AS cas_snapshot_first_name, + COALESCE( + cas_supplier.student_profile_snapshot ->> ''firstName'', + '''' + ) AS cas_snapshot_first_name, cas_supplier.student_profile_snapshot ->> ''lastName'' AS cas_snapshot_last_name, cas_supplier.student_profile_snapshot ->> ''sin'' AS cas_snapshot_sin, cas_supplier.student_profile_snapshot ->> ''addressLine1'' AS cas_snapshot_address_line_1, cas_supplier.student_profile_snapshot ->> ''city'' AS cas_snapshot_city, - cas_supplier.student_profile_snapshot ->> ''province'' AS cas_snapshot_province, + COALESCE( + cas_supplier.student_profile_snapshot ->> ''province'', + '''' + ) AS cas_snapshot_province, cas_supplier.student_profile_snapshot ->> ''postalCode'' AS cas_snapshot_postal_code, cas_supplier.student_profile_snapshot ->> ''country'' AS cas_snapshot_country FROM @@ -37,72 +46,22 @@ VALUES WHERE cas_supplier.is_valid = true AND ( - ( - ( - cas_supplier.student_profile_snapshot ->> ''firstName'' - ) IS DISTINCT - FROM - student_user.first_name - ) - OR ( - ( - cas_supplier.student_profile_snapshot ->> ''lastName'' - ) IS DISTINCT - FROM - student_user.last_name - ) - OR ( - ( - cas_supplier.student_profile_snapshot ->> ''sin'' - ) IS DISTINCT - FROM - sin_validation.sin - ) - OR ( - ( - cas_supplier.student_profile_snapshot ->> ''addressLine1'' - ) IS DISTINCT - FROM - ( - student.contact_info -> ''address'' ->> ''addressLine1'' - ) - ) - OR ( - ( - cas_supplier.student_profile_snapshot ->> ''city'' - ) IS DISTINCT - FROM - ( - student.contact_info -> ''address'' ->> ''city'' - ) - ) - OR ( - ( - cas_supplier.student_profile_snapshot ->> ''province'' - ) IS DISTINCT - FROM - ( - student.contact_info -> ''address'' ->> ''provinceState'' - ) - ) - OR ( - ( - cas_supplier.student_profile_snapshot ->> ''postalCode'' - ) IS DISTINCT - FROM - ( - student.contact_info -> ''address'' ->> ''postalCode'' - ) - ) - OR ( - ( - cas_supplier.student_profile_snapshot ->> ''country'' - ) IS DISTINCT - FROM - ( - student.contact_info -> ''address'' ->> ''country'' - ) + COALESCE( + cas_supplier.student_profile_snapshot ->> ''firstName'', + '''' + ) NOT ILIKE COALESCE(student_user.first_name, '''') + OR cas_supplier.student_profile_snapshot ->> ''lastName'' NOT ILIKE student_user.last_name + OR cas_supplier.student_profile_snapshot ->> ''sin'' NOT ILIKE sin_validation.sin + OR cas_supplier.student_profile_snapshot ->> ''addressLine1'' NOT ILIKE student.contact_info -> ''address'' ->> ''addressLine1'' + OR cas_supplier.student_profile_snapshot ->> ''city'' NOT ILIKE student.contact_info -> ''address'' ->> ''city'' + OR COALESCE( + cas_supplier.student_profile_snapshot ->> ''province'', + '''' + ) NOT ILIKE COALESCE( + student.contact_info -> ''address'' ->> ''provinceState'' ) + OR cas_supplier.student_profile_snapshot ->> ''postalCode'' NOT ILIKE student.contact_info -> ''address'' ->> ''postalCode'' + OR cas_supplier.student_profile_snapshot ->> ''country'' NOT ILIKE student.contact_info -> ''address'' ->> ''country'' ) ) SELECT @@ -128,70 +87,14 @@ VALUES cas_supplier_report.cas_supplier_verified_date, ''YYYY - MM - DD'' ) AS "Supplier Verified Date", - CASE - WHEN ( - cas_supplier_report.student_first_name IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_first_name - ) THEN ''Yes'' - ELSE ''No'' - END "First Name Updated", - CASE - WHEN ( - cas_supplier_report.student_last_name IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_last_name - ) THEN ''Yes'' - ELSE ''No'' - END "Last Name Updated", - CASE - WHEN ( - cas_supplier_report.student_sin IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_sin - ) THEN ''Yes'' - ELSE ''No'' - END "SIN Updated", - CASE - WHEN ( - cas_supplier_report.student_address_line_1 IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_address_line_1 - ) THEN ''Yes'' - ELSE ''No'' - END "Address Line 1 Updated", - CASE - WHEN ( - cas_supplier_report.student_city IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_city - ) THEN ''Yes'' - ELSE ''No'' - END "City Updated", - CASE - WHEN ( - cas_supplier_report.student_province IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_province - ) THEN ''Yes'' - ELSE ''No'' - END "Province Updated", - CASE - WHEN ( - cas_supplier_report.student_postal_code IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_postal_code - ) THEN ''Yes'' - ELSE ''No'' - END "Postal Code Updated", - CASE - WHEN ( - cas_supplier_report.student_country IS DISTINCT - FROM - cas_supplier_report.cas_snapshot_country - ) THEN ''Yes'' - ELSE ''No'' - END "Country Updated" + cas_supplier_report.student_first_name NOT ILIKE cas_supplier_report.cas_snapshot_first_name AS "First Name Updated", + cas_supplier_report.student_last_name NOT ILIKE cas_supplier_report.cas_snapshot_last_name AS "Last Name Updated", + cas_supplier_report.student_sin NOT ILIKE cas_supplier_report.cas_snapshot_sin AS "SIN Updated", + cas_supplier_report.student_address_line_1 NOT ILIKE cas_supplier_report.cas_snapshot_address_line_1 AS "Address Line 1 Updated", + cas_supplier_report.student_city NOT ILIKE cas_supplier_report.cas_snapshot_city AS "City Updated", + cas_supplier_report.student_province NOT ILIKE cas_supplier_report.cas_snapshot_province AS "Province Updated", + cas_supplier_report.student_postal_code NOT ILIKE cas_supplier_report.cas_snapshot_postal_code AS "Postal Code Updated", + cas_supplier_report.student_country NOT ILIKE cas_supplier_report.cas_snapshot_country AS "Country Updated" FROM cas_supplier_report ORDER BY From e980bdb96cff20082f44a2c95ca201b4316178a2 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Fri, 6 Dec 2024 09:50:05 -0700 Subject: [PATCH 04/14] updated the migration --- ...as-supplier-maintenance-updates-report.sql | 63 +++++++++++++------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql index 0e735d13a9..9deea20f59 100644 --- a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql +++ b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql @@ -28,16 +28,34 @@ VALUES cas_supplier.student_profile_snapshot ->> ''firstName'', '''' ) AS cas_snapshot_first_name, - cas_supplier.student_profile_snapshot ->> ''lastName'' AS cas_snapshot_last_name, - cas_supplier.student_profile_snapshot ->> ''sin'' AS cas_snapshot_sin, - cas_supplier.student_profile_snapshot ->> ''addressLine1'' AS cas_snapshot_address_line_1, - cas_supplier.student_profile_snapshot ->> ''city'' AS cas_snapshot_city, + COALESCE( + cas_supplier.student_profile_snapshot ->> ''lastName'', + '''' + ) AS cas_snapshot_last_name, + COALESCE( + cas_supplier.student_profile_snapshot ->> ''sin'', + '''' + ) AS cas_snapshot_sin, + COALESCE( + cas_supplier.student_profile_snapshot ->> ''addressLine1'', + '''' + ) AS cas_snapshot_address_line_1, + COALESCE( + cas_supplier.student_profile_snapshot ->> ''city'', + '''' + ) AS cas_snapshot_city, COALESCE( cas_supplier.student_profile_snapshot ->> ''province'', '''' ) AS cas_snapshot_province, - cas_supplier.student_profile_snapshot ->> ''postalCode'' AS cas_snapshot_postal_code, - cas_supplier.student_profile_snapshot ->> ''country'' AS cas_snapshot_country + COALESCE( + cas_supplier.student_profile_snapshot ->> ''postalCode'', + '''' + ) AS cas_snapshot_postal_code, + COALESCE( + cas_supplier.student_profile_snapshot ->> ''country'', + '''' + ) AS cas_snapshot_country FROM sims.students student INNER JOIN sims.cas_suppliers cas_supplier on student.cas_supplier_id = cas_supplier.id @@ -46,22 +64,27 @@ VALUES WHERE cas_supplier.is_valid = true AND ( - COALESCE( - cas_supplier.student_profile_snapshot ->> ''firstName'', - '''' - ) NOT ILIKE COALESCE(student_user.first_name, '''') - OR cas_supplier.student_profile_snapshot ->> ''lastName'' NOT ILIKE student_user.last_name - OR cas_supplier.student_profile_snapshot ->> ''sin'' NOT ILIKE sin_validation.sin - OR cas_supplier.student_profile_snapshot ->> ''addressLine1'' NOT ILIKE student.contact_info -> ''address'' ->> ''addressLine1'' - OR cas_supplier.student_profile_snapshot ->> ''city'' NOT ILIKE student.contact_info -> ''address'' ->> ''city'' - OR COALESCE( - cas_supplier.student_profile_snapshot ->> ''province'', + jsonb_build_object( + ''firstName'', + student_user.first_name, + ''lastName'', + student_user.last_name, + ''sin'', + sin_validation.sin, + ''addressLine1'', + student.contact_info -> ''address'' ->> ''addressLine1'', + ''city'', + student.contact_info -> ''address'' ->> ''city'', + ''province'', + student.contact_info -> ''address'' ->> ''provinceState'', + ''postalCode'', + student.contact_info -> ''address'' ->> ''postalCode'', + ''country'', + student.contact_info -> ''address'' ->> ''country'' + ) :: text NOT ILIKE COALESCE( + cas_supplier.student_profile_snapshot :: text, '''' - ) NOT ILIKE COALESCE( - student.contact_info -> ''address'' ->> ''provinceState'' ) - OR cas_supplier.student_profile_snapshot ->> ''postalCode'' NOT ILIKE student.contact_info -> ''address'' ->> ''postalCode'' - OR cas_supplier.student_profile_snapshot ->> ''country'' NOT ILIKE student.contact_info -> ''address'' ->> ''country'' ) ) SELECT From 6b87260d7ff2d30ab80aeabc477cc25d32338652 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Fri, 6 Dec 2024 09:55:53 -0700 Subject: [PATCH 05/14] Updated the factory to have the CAS Suppier Link --- .../backend/libs/test-utils/src/factories/cas-supplier.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts b/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts index e38f17352a..5179a7e78e 100644 --- a/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts +++ b/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts @@ -38,7 +38,9 @@ export async function saveFakeCASSupplier( student = await saveFakeStudent(db.dataSource); } const casSupplier = createFakeCASSupplier({ student, auditUser }, options); - return db.casSupplier.save(casSupplier); + student.casSupplier = casSupplier; + await db.student.save(student); + return student.casSupplier; } /** From f6609e735fb07990ef4e73a62320bf96a2139d17 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Fri, 6 Dec 2024 11:21:03 -0700 Subject: [PATCH 06/14] Updated Factory to Save Snapshot details --- .../cas-supplier/cas-supplier.shared.service.ts | 2 +- .../test-utils/src/factories/cas-supplier.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts b/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts index d3ee5766fe..b3c2e0df35 100644 --- a/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts +++ b/sources/packages/backend/libs/services/src/cas-supplier/cas-supplier.shared.service.ts @@ -12,7 +12,7 @@ export class CASSupplierSharedService { * @returns student profile snapshot. */ getStudentProfileSnapshot( - firstName: string = null, + firstName: string, lastName: string, sin: string, addressInfo: AddressInfo, diff --git a/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts b/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts index 5179a7e78e..5ed0daa9a2 100644 --- a/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts +++ b/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts @@ -1,6 +1,7 @@ import { CASSupplier, Student, + StudentProfileSnapshot, SupplierAddress, SupplierStatus, User, @@ -98,6 +99,21 @@ export function createFakeCASSupplier( casSupplier.supplierStatusUpdatedOn = new Date(); casSupplier.creator = relations.auditUser; casSupplier.student = relations.student; + // Build the student profile snapshot based on valid status. + const studentProfileSnapshot: StudentProfileSnapshot = casSupplier.isValid + ? { + firstName: relations.student.user.firstName, + lastName: relations.student.user.lastName, + sin: relations.student.sinValidation.sin, + addressLine1: relations.student.contactInfo.address.addressLine1, + city: relations.student.contactInfo.address.city, + province: relations.student.contactInfo.address.provinceState, + postalCode: relations.student.contactInfo.address.postalCode, + country: relations.student.contactInfo.address.country, + } + : null; + casSupplier.studentProfileSnapshot = + options?.initialValues?.studentProfileSnapshot ?? studentProfileSnapshot; return casSupplier; } From 0a657c5fa7932c2bc6bf7bc3bc3774e0251fa934 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Fri, 6 Dec 2024 13:05:19 -0700 Subject: [PATCH 07/14] Updated Factory to Save Snapshot details --- .../e2e/report.aest.controller.exportReport.e2e-spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts index 763200e55c..ca3158109e 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts @@ -38,6 +38,7 @@ import { InstitutionLocation, OfferingIntensity, ProgramIntensity, + Student, WorkflowData, } from "@sims/sims-db"; import { addDays, getISODateOnlyString } from "@sims/utilities"; @@ -51,6 +52,7 @@ describe("ReportAestController(e2e)-exportReport", () => { let db: E2EDataSources; let appDataSource: DataSource; let formService: FormService; + let sharedCASSupplierUpdatedStudent: Student; beforeAll(async () => { const { nestApplication, module, dataSource } = @@ -65,6 +67,7 @@ describe("ReportAestController(e2e)-exportReport", () => { AppAESTModule, FormService, ); + sharedCASSupplierUpdatedStudent = await saveFakeStudent(db.dataSource); }); it("Should generate the eCert Feedback Errors report when a report generation request is made with the appropriate offering intensity and date range.", async () => { From c4f91e17443d3431134ce98f77c3a2549fcbd069 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 10 Dec 2024 16:32:19 -0700 Subject: [PATCH 08/14] E2E Tests --- ...t.aest.controller.exportReport.e2e-spec.ts | 122 ++++++++++++++++++ ...as-supplier-maintenance-updates-report.sql | 4 +- .../test-utils/src/factories/cas-supplier.ts | 2 +- 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts index ca3158109e..74ef441417 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts @@ -39,6 +39,7 @@ import { OfferingIntensity, ProgramIntensity, Student, + SupplierStatus, WorkflowData, } from "@sims/sims-db"; import { addDays, getISODateOnlyString } from "@sims/utilities"; @@ -67,6 +68,7 @@ describe("ReportAestController(e2e)-exportReport", () => { AppAESTModule, FormService, ); + await db.casSupplier.update({ isValid: true }, { isValid: false }); sharedCASSupplierUpdatedStudent = await saveFakeStudent(db.dataSource); }); @@ -1429,6 +1431,69 @@ describe("ReportAestController(e2e)-exportReport", () => { }); }); + it( + "Should generate CAS Supplier maintenance updates report with the student details" + + " when last name of the student is updated after the CAS supplier is set to be valid.", + async () => { + // Arrange + // Save a CASSupplier for the student. + await saveFakeCASSupplier( + db, + { student: sharedCASSupplierUpdatedStudent }, + { + initialValues: { + supplierStatus: SupplierStatus.Verified, + isValid: true, + }, + }, + ); + // Update the student's last name. + sharedCASSupplierUpdatedStudent.user.lastName = "Updated Last Name"; + await db.student.save(sharedCASSupplierUpdatedStudent); + + const casSupplierMaintenanceUpdates = + "CAS_Supplier_Maintenance_Updates_Report"; + const endpoint = "/aest/report"; + const ministryUserToken = await getAESTToken( + AESTGroups.BusinessAdministrators, + ); + + // Payload with no parameters. + const payload = { + reportName: casSupplierMaintenanceUpdates, + params: {}, + }; + const dryRunSubmissionMock = jest.fn().mockResolvedValue({ + valid: true, + formName: FormNames.ExportFinancialReports, + data: { data: payload }, + }); + formService.dryRunSubmission = dryRunSubmissionMock; + + // 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, + }); + const expectedReportData = buildCASSupplierMaintenanceUpdatesReport( + sharedCASSupplierUpdatedStudent, + { lastNameUpdated: true }, + ); + const [actualReportData] = parsedResult.data as Record< + string, + string + >[]; + expect(actualReportData).toEqual(expectedReportData); + }); + }, + ); + /** * Converts education program offering object into a key-value pair object matching the result data. * @param fakeOffering an education program offering record. @@ -1495,4 +1560,61 @@ describe("ReportAestController(e2e)-exportReport", () => { "Offering Intensity": "", }; } + + /** + * Build CAS Supplier maintenance updates report data. + * @param student student. + * @param options expected report data options. + * - `firstNameUpdated` indicates if the first name of the student is updated. + * - `lastNameUpdated` indicates if the last name of the student is updated. + * - `sinUpdated` indicates if the SIN number of the student is updated. + * - `addressLine1Updated` indicates if the address line 1 of the student is updated. + * - `cityUpdated` indicates if the city of the student is updated. + * - `provinceUpdated` indicates if the province of the student is updated. + * - `postalCodeUpdated` indicates if the postal code of the student is updated. + * - `countryUpdated` indicates if the country of the student is updated. + * @returns report data. + */ + function buildCASSupplierMaintenanceUpdatesReport( + student: Student, + options?: { + firstNameUpdated?: boolean; + lastNameUpdated?: boolean; + sinUpdated?: boolean; + addressLine1Updated?: boolean; + cityUpdated?: boolean; + provinceUpdated?: boolean; + postalCodeUpdated?: boolean; + countryUpdated?: boolean; + }, + ): Record { + return { + "Given Names": student.user.firstName, + "Last Name": student.user.lastName, + SIN: student.sinValidation.sin, + "Address Line 1": student.contactInfo.address.addressLine1, + City: student.contactInfo.address.city, + Province: student.contactInfo.address.provinceState, + "Postal Code": student.contactInfo.address.postalCode, + Country: student.contactInfo.address.country, + "Disability Status": student.disabilityStatus, + "Profile Type": student.user.identityProviderType ?? "", + "Student Updated Date": getISODateOnlyString(student.updatedAt), + Supplier: student.casSupplier.supplierNumber, + Site: student.casSupplier.supplierAddress.supplierSiteCode, + "Protected Supplier": student.casSupplier.supplierProtected?.toString(), + "Protected Site": student.casSupplier.supplierAddress.siteProtected, + "Supplier Verified Date": getISODateOnlyString( + student.casSupplier.supplierStatusUpdatedOn, + ), + "First Name Updated": options?.firstNameUpdated ? "true" : "false", + "Last Name Updated": options?.lastNameUpdated ? "true" : "false", + "SIN Updated": options?.sinUpdated ? "true" : "false", + "Address Line 1 Updated": options?.addressLine1Updated ? "true" : "false", + "City Updated": options?.cityUpdated ? "true" : "false", + "Province Updated": options?.provinceUpdated ? "true" : "false", + "Postal Code Updated": options?.postalCodeUpdated ? "true" : "false", + "Country Updated": options?.countryUpdated ? "true" : "false", + }; + } }); diff --git a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql index 9deea20f59..42786fc439 100644 --- a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql +++ b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql @@ -100,7 +100,7 @@ VALUES cas_supplier_report.student_profile_type AS "Profile Type", TO_CHAR( cas_supplier_report.student_updated_date, - ''YYYY - MM - DD'' + ''YYYY-MM-DD'' ) AS "Student Updated Date", cas_supplier_report.cas_supplier AS "Supplier", cas_supplier_report.cas_site AS "Site", @@ -108,7 +108,7 @@ VALUES cas_supplier_report.cas_site_protected AS "Protected Site", TO_CHAR( cas_supplier_report.cas_supplier_verified_date, - ''YYYY - MM - DD'' + ''YYYY-MM-DD'' ) AS "Supplier Verified Date", cas_supplier_report.student_first_name NOT ILIKE cas_supplier_report.cas_snapshot_first_name AS "First Name Updated", cas_supplier_report.student_last_name NOT ILIKE cas_supplier_report.cas_snapshot_last_name AS "Last Name Updated", diff --git a/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts b/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts index 5c5cd8d471..3c0d91906e 100644 --- a/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts +++ b/sources/packages/backend/libs/test-utils/src/factories/cas-supplier.ts @@ -89,7 +89,7 @@ export function createFakeCASSupplier( lastUpdated: new Date(), }; casSupplier.supplierProtected = true; - casSupplier.isValid = false; + casSupplier.isValid = options?.initialValues.isValid ?? false; } casSupplier.supplierName = `${faker.name.lastName()}, ${faker.name.firstName()}`; casSupplier.supplierNumber = faker.datatype From d82abc16c57443a14c90d6482cb80dc4ad75d159 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 10 Dec 2024 18:59:02 -0700 Subject: [PATCH 09/14] Update E2E all scenarios --- ...t.aest.controller.exportReport.e2e-spec.ts | 249 +++++++++++++++++- .../report/report.controller.service.ts | 12 +- .../libs/utilities/src/string-utils.ts | 13 +- 3 files changed, 265 insertions(+), 9 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts index 74ef441417..ea579a102f 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts @@ -1432,7 +1432,7 @@ describe("ReportAestController(e2e)-exportReport", () => { }); it( - "Should generate CAS Supplier maintenance updates report with the student details" + + "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.", async () => { // Arrange @@ -1494,6 +1494,253 @@ describe("ReportAestController(e2e)-exportReport", () => { }, ); + it( + "Should generate CAS Supplier maintenance updates report with the student details of the given student" + + " when SIN number of the student is updated after the CAS supplier is set to be valid.", + async () => { + // Arrange + // Save a CASSupplier for the student. + await saveFakeCASSupplier( + db, + { student: sharedCASSupplierUpdatedStudent }, + { + initialValues: { + supplierStatus: SupplierStatus.Verified, + isValid: true, + }, + }, + ); + // Update the student's SIN. + const newSINValidation = createFakeSINValidation({ + student: sharedCASSupplierUpdatedStudent, + }); + sharedCASSupplierUpdatedStudent.sinValidation = newSINValidation; + await db.student.save(sharedCASSupplierUpdatedStudent); + + const casSupplierMaintenanceUpdates = + "CAS_Supplier_Maintenance_Updates_Report"; + const endpoint = "/aest/report"; + const ministryUserToken = await getAESTToken( + AESTGroups.BusinessAdministrators, + ); + + // Payload with no parameters. + const payload = { + reportName: casSupplierMaintenanceUpdates, + params: {}, + }; + const dryRunSubmissionMock = jest.fn().mockResolvedValue({ + valid: true, + formName: FormNames.ExportFinancialReports, + data: { data: payload }, + }); + formService.dryRunSubmission = dryRunSubmissionMock; + + // 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, + }); + const expectedReportData = buildCASSupplierMaintenanceUpdatesReport( + sharedCASSupplierUpdatedStudent, + { sinUpdated: true }, + ); + const [actualReportData] = parsedResult.data as Record< + string, + string + >[]; + expect(actualReportData).toEqual(expectedReportData); + }); + }, + ); + + it( + "Should generate CAS Supplier maintenance updates report with the student details of the given student" + + " when 'address line 1' of the student is updated after the CAS supplier is set to be valid.", + async () => { + // Arrange + // Save a CASSupplier for the student. + await saveFakeCASSupplier( + db, + { student: sharedCASSupplierUpdatedStudent }, + { + initialValues: { + supplierStatus: SupplierStatus.Verified, + isValid: true, + }, + }, + ); + // Update the student's address line 1. + sharedCASSupplierUpdatedStudent.contactInfo.address.addressLine1 = + "Updated Address Line 1"; + await db.student.save(sharedCASSupplierUpdatedStudent); + + const casSupplierMaintenanceUpdates = + "CAS_Supplier_Maintenance_Updates_Report"; + const endpoint = "/aest/report"; + const ministryUserToken = await getAESTToken( + AESTGroups.BusinessAdministrators, + ); + + // Payload with no parameters. + const payload = { + reportName: casSupplierMaintenanceUpdates, + params: {}, + }; + const dryRunSubmissionMock = jest.fn().mockResolvedValue({ + valid: true, + formName: FormNames.ExportFinancialReports, + data: { data: payload }, + }); + formService.dryRunSubmission = dryRunSubmissionMock; + + // 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, + }); + const expectedReportData = buildCASSupplierMaintenanceUpdatesReport( + sharedCASSupplierUpdatedStudent, + { addressLine1Updated: true }, + ); + const [actualReportData] = parsedResult.data as Record< + string, + string + >[]; + expect(actualReportData).toEqual(expectedReportData); + }); + }, + ); + + it( + "Should generate CAS Supplier maintenance updates report with the student details of the given student" + + " when postal code of the student is updated after the CAS supplier is set to be valid.", + async () => { + // Arrange + // Save a CASSupplier for the student. + await saveFakeCASSupplier( + db, + { student: sharedCASSupplierUpdatedStudent }, + { + initialValues: { + supplierStatus: SupplierStatus.Verified, + isValid: true, + }, + }, + ); + // Update the student's postal code. + sharedCASSupplierUpdatedStudent.contactInfo.address.postalCode = + "Updated postal code"; + await db.student.save(sharedCASSupplierUpdatedStudent); + + const casSupplierMaintenanceUpdates = + "CAS_Supplier_Maintenance_Updates_Report"; + const endpoint = "/aest/report"; + const ministryUserToken = await getAESTToken( + AESTGroups.BusinessAdministrators, + ); + + // Payload with no parameters. + const payload = { + reportName: casSupplierMaintenanceUpdates, + params: {}, + }; + const dryRunSubmissionMock = jest.fn().mockResolvedValue({ + valid: true, + formName: FormNames.ExportFinancialReports, + data: { data: payload }, + }); + formService.dryRunSubmission = dryRunSubmissionMock; + + // 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, + }); + const expectedReportData = buildCASSupplierMaintenanceUpdatesReport( + sharedCASSupplierUpdatedStudent, + { postalCodeUpdated: true }, + ); + const [actualReportData] = parsedResult.data as Record< + string, + string + >[]; + expect(actualReportData).toEqual(expectedReportData); + }); + }, + ); + + it( + "Should generate CAS Supplier maintenance updates report without the student details of the given student" + + " when last name of the student is updated just to change the case to upper case characters.", + async () => { + // Arrange + // Save a CASSupplier for the student. + await saveFakeCASSupplier( + db, + { student: sharedCASSupplierUpdatedStudent }, + { + initialValues: { + supplierStatus: SupplierStatus.Verified, + isValid: true, + }, + }, + ); + // Update the student's last name to upper case. + sharedCASSupplierUpdatedStudent.user.lastName = + sharedCASSupplierUpdatedStudent.user.lastName.toUpperCase(); + await db.student.save(sharedCASSupplierUpdatedStudent); + + const casSupplierMaintenanceUpdates = + "CAS_Supplier_Maintenance_Updates_Report"; + const endpoint = "/aest/report"; + const ministryUserToken = await getAESTToken( + AESTGroups.BusinessAdministrators, + ); + + // Payload with no parameters. + const payload = { + reportName: casSupplierMaintenanceUpdates, + params: {}, + }; + const dryRunSubmissionMock = jest.fn().mockResolvedValue({ + valid: true, + formName: FormNames.ExportFinancialReports, + data: { data: payload }, + }); + formService.dryRunSubmission = dryRunSubmissionMock; + + // 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"] as string; + expect(fileContent).toBe(""); + }); + }, + ); + /** * Converts education program offering object into a key-value pair object matching the result data. * @param fakeOffering an education program offering record. diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts index d380770da6..8ab3bb7998 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts @@ -11,7 +11,7 @@ import { import { getFileNameAsCurrentTimestamp, CustomNamedError, - UTF8_BYTE_ORDER_MARK, + appendByteOrderMark, } from "@sims/utilities"; import { Response } from "express"; import { Readable } from "stream"; @@ -99,12 +99,10 @@ export class ReportControllerService { const filename = `${reportName}_${timestamp}.csv`; // Adding byte order mark characters to the original file content as applications // like excel would look for BOM characters to view the file as UTF8 encoded. - const byteOrderMarkBuffer = Buffer.from(UTF8_BYTE_ORDER_MARK); - const fileContentBuffer = Buffer.from(fileContent); - const responseBuffer = Buffer.concat([ - byteOrderMarkBuffer, - fileContentBuffer, - ]); + // Append byte order mark characters only if the file content is not empty. + const responseBuffer = !!fileContent + ? appendByteOrderMark(fileContent) + : Buffer.from(""); response.setHeader( "Content-Disposition", `attachment; filename=${filename}`, diff --git a/sources/packages/backend/libs/utilities/src/string-utils.ts b/sources/packages/backend/libs/utilities/src/string-utils.ts index 34b4fbea48..185bb5236e 100644 --- a/sources/packages/backend/libs/utilities/src/string-utils.ts +++ b/sources/packages/backend/libs/utilities/src/string-utils.ts @@ -1,4 +1,4 @@ -import { FILE_DEFAULT_ENCODING } from "@sims/utilities"; +import { FILE_DEFAULT_ENCODING, UTF8_BYTE_ORDER_MARK } from "@sims/utilities"; const REPLACE_LINE_BREAK_REGEX = /\r?\n|\r/g; @@ -154,3 +154,14 @@ export function convertToASCII(rawContent?: string): Buffer | null { export function convertToASCIIString(rawContent?: string): string | null { return convertToASCII(rawContent)?.toString() ?? null; } + +/** + * Append UTF-8 BOM to the content. + * @param content content to append the BOM. + * @returns content with the BOM appended. + */ +export function appendByteOrderMark(content: string): Buffer { + const byteOrderMarkBuffer = Buffer.from(UTF8_BYTE_ORDER_MARK); + const fileContentBuffer = Buffer.from(content); + return Buffer.concat([byteOrderMarkBuffer, fileContentBuffer]); +} From 49c36dcc85d3522d7867db431917496d933825f2 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 10 Dec 2024 19:52:23 -0700 Subject: [PATCH 10/14] Update E2E all scenarios --- .../e2e/report.aest.controller.exportReport.e2e-spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts index ea579a102f..14222803cf 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts @@ -68,7 +68,7 @@ describe("ReportAestController(e2e)-exportReport", () => { AppAESTModule, FormService, ); - await db.casSupplier.update({ isValid: true }, { isValid: false }); + // Shared student used for CAS Supplier maintenance updates report. sharedCASSupplierUpdatedStudent = await saveFakeStudent(db.dataSource); }); @@ -1489,6 +1489,7 @@ describe("ReportAestController(e2e)-exportReport", () => { string, string >[]; + expect(parsedResult.data.length).toBe(1); expect(actualReportData).toEqual(expectedReportData); }); }, @@ -1555,6 +1556,7 @@ describe("ReportAestController(e2e)-exportReport", () => { string, string >[]; + expect(parsedResult.data.length).toBe(1); expect(actualReportData).toEqual(expectedReportData); }); }, @@ -1619,6 +1621,7 @@ describe("ReportAestController(e2e)-exportReport", () => { string, string >[]; + expect(parsedResult.data.length).toBe(1); expect(actualReportData).toEqual(expectedReportData); }); }, @@ -1683,6 +1686,7 @@ describe("ReportAestController(e2e)-exportReport", () => { string, string >[]; + expect(parsedResult.data.length).toBe(1); expect(actualReportData).toEqual(expectedReportData); }); }, From fee5b9b3333cbcd82f1b0eaf7d6f38926cddb68d Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 10 Dec 2024 22:54:05 -0700 Subject: [PATCH 11/14] Updated Query for more precise comparison --- ...as-supplier-maintenance-updates-report.sql | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql index 42786fc439..44482ed45d 100644 --- a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql +++ b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql @@ -81,10 +81,24 @@ VALUES student.contact_info -> ''address'' ->> ''postalCode'', ''country'', student.contact_info -> ''address'' ->> ''country'' - ) :: text NOT ILIKE COALESCE( - cas_supplier.student_profile_snapshot :: text, - '''' - ) + ) :: text NOT ILIKE jsonb_build_object( + ''firstName'', + cas_supplier.student_profile_snapshot ->> ''firstName'', + ''lastName'', + cas_supplier.student_profile_snapshot ->> ''lastName'', + ''sin'', + cas_supplier.student_profile_snapshot ->> ''sin'', + ''addressLine1'', + cas_supplier.student_profile_snapshot ->> ''addressLine1'', + ''city'', + cas_supplier.student_profile_snapshot ->> ''city'', + ''province'', + cas_supplier.student_profile_snapshot ->> ''province'', + ''postalCode'', + cas_supplier.student_profile_snapshot ->> ''postalCode'', + ''country'', + cas_supplier.student_profile_snapshot ->> ''country'' + ) :: text ) ) SELECT From ed6c95bf8f2c46a77772dc4a7e9698693e3bd5ca Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Wed, 11 Dec 2024 09:22:11 -0700 Subject: [PATCH 12/14] Change as per Sonar Cloud compliance --- .../src/route-controllers/report/report.controller.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts index 8ab3bb7998..cec3371f76 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/report.controller.service.ts @@ -100,7 +100,7 @@ export class ReportControllerService { // Adding byte order mark characters to the original file content as applications // like excel would look for BOM characters to view the file as UTF8 encoded. // Append byte order mark characters only if the file content is not empty. - const responseBuffer = !!fileContent + const responseBuffer = fileContent ? appendByteOrderMark(fileContent) : Buffer.from(""); response.setHeader( From 7764bef5c7bda5cf9ff642ab17ace4aea6e1c64f Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Wed, 11 Dec 2024 11:06:54 -0700 Subject: [PATCH 13/14] review comments --- ...report.aest.controller.exportReport.e2e-spec.ts | 5 +++-- ...ate-cas-supplier-maintenance-updates-report.sql | 4 ++-- .../libs/test-utils/src/utils/date-utils.ts | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts index 14222803cf..a30be900d1 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts @@ -46,6 +46,7 @@ import { addDays, getISODateOnlyString } from "@sims/utilities"; import { DataSource } from "typeorm"; import { createFakeEducationProgram } from "@sims/test-utils/factories/education-program"; import { createFakeSINValidation } from "@sims/test-utils/factories/sin-validation"; +import { getPSTPDTDateFormatted } from "@sims/test-utils/utils"; describe("ReportAestController(e2e)-exportReport", () => { let app: INestApplication; @@ -1850,12 +1851,12 @@ describe("ReportAestController(e2e)-exportReport", () => { Country: student.contactInfo.address.country, "Disability Status": student.disabilityStatus, "Profile Type": student.user.identityProviderType ?? "", - "Student Updated Date": getISODateOnlyString(student.updatedAt), + "Student Updated Date": getPSTPDTDateFormatted(student.updatedAt), Supplier: student.casSupplier.supplierNumber, Site: student.casSupplier.supplierAddress.supplierSiteCode, "Protected Supplier": student.casSupplier.supplierProtected?.toString(), "Protected Site": student.casSupplier.supplierAddress.siteProtected, - "Supplier Verified Date": getISODateOnlyString( + "Supplier Verified Date": getPSTPDTDateFormatted( student.casSupplier.supplierStatusUpdatedOn, ), "First Name Updated": options?.firstNameUpdated ? "true" : "false", diff --git a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql index 44482ed45d..776f2dbaf0 100644 --- a/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql +++ b/sources/packages/backend/apps/db-migrations/src/sql/Reports/Create-cas-supplier-maintenance-updates-report.sql @@ -113,7 +113,7 @@ VALUES cas_supplier_report.student_disability_status AS "Disability Status", cas_supplier_report.student_profile_type AS "Profile Type", TO_CHAR( - cas_supplier_report.student_updated_date, + (cas_supplier_report.student_updated_date AT TIME ZONE ''America/Vancouver''), ''YYYY-MM-DD'' ) AS "Student Updated Date", cas_supplier_report.cas_supplier AS "Supplier", @@ -121,7 +121,7 @@ VALUES cas_supplier_report.cas_supplier_protected AS "Protected Supplier", cas_supplier_report.cas_site_protected AS "Protected Site", TO_CHAR( - cas_supplier_report.cas_supplier_verified_date, + (cas_supplier_report.cas_supplier_verified_date AT TIME ZONE ''America/Vancouver''), ''YYYY-MM-DD'' ) AS "Supplier Verified Date", cas_supplier_report.student_first_name NOT ILIKE cas_supplier_report.cas_snapshot_first_name AS "First Name Updated", diff --git a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts index d447e413ec..2ddb5395a9 100644 --- a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts +++ b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts @@ -1,3 +1,4 @@ +import { DATE_ONLY_ISO_FORMAT, PST_TIMEZONE } from "@sims/utilities"; import * as dayjs from "dayjs"; /** @@ -35,3 +36,16 @@ export const addMilliSeconds = ( .add(milliSecondsToAdd, "millisecond") .toDate(); }; + +/** + * Convert the date to PST/PDT(PST: UTC−08:00, PDT: UTC−07:00) and format. + * @param date date. + * @param format date format. + * @returns converted date in the given format. + */ +export function getPSTPDTDateFormatted( + date: Date | string, + format = DATE_ONLY_ISO_FORMAT, +): string { + return dayjs(new Date(date)).tz(PST_TIMEZONE, false).format(format); +} From 10f5160f9aa0dffc2faa323166e7a7ea2cdfe69f Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Wed, 11 Dec 2024 11:18:16 -0700 Subject: [PATCH 14/14] review comments --- ...t.aest.controller.exportReport.e2e-spec.ts | 62 +++++-------------- .../web/src/constants/report-constants.ts | 8 +-- 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts index a30be900d1..49f1932cf5 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/report/_tests_/e2e/report.aest.controller.exportReport.e2e-spec.ts @@ -47,6 +47,7 @@ import { DataSource } from "typeorm"; import { createFakeEducationProgram } from "@sims/test-utils/factories/education-program"; import { createFakeSINValidation } from "@sims/test-utils/factories/sin-validation"; import { getPSTPDTDateFormatted } from "@sims/test-utils/utils"; +import { MinistryReportsFilterAPIInDTO } from "apps/api/src/route-controllers/report/models/report.dto"; describe("ReportAestController(e2e)-exportReport", () => { let app: INestApplication; @@ -55,6 +56,7 @@ describe("ReportAestController(e2e)-exportReport", () => { let appDataSource: DataSource; let formService: FormService; let sharedCASSupplierUpdatedStudent: Student; + let casSupplierMaintenanceUpdatesPayload: MinistryReportsFilterAPIInDTO; beforeAll(async () => { const { nestApplication, module, dataSource } = @@ -71,6 +73,11 @@ describe("ReportAestController(e2e)-exportReport", () => { ); // Shared student used for CAS Supplier maintenance updates report. sharedCASSupplierUpdatedStudent = await saveFakeStudent(db.dataSource); + // Build payload for CAS Supplier maintenance updates report to use across tests. + casSupplierMaintenanceUpdatesPayload = { + reportName: "CAS_Supplier_Maintenance_Updates_Report", + params: {}, + }; }); it("Should generate the eCert Feedback Errors report when a report generation request is made with the appropriate offering intensity and date range.", async () => { @@ -1452,29 +1459,22 @@ describe("ReportAestController(e2e)-exportReport", () => { sharedCASSupplierUpdatedStudent.user.lastName = "Updated Last Name"; await db.student.save(sharedCASSupplierUpdatedStudent); - const casSupplierMaintenanceUpdates = - "CAS_Supplier_Maintenance_Updates_Report"; const endpoint = "/aest/report"; const ministryUserToken = await getAESTToken( AESTGroups.BusinessAdministrators, ); - // Payload with no parameters. - const payload = { - reportName: casSupplierMaintenanceUpdates, - params: {}, - }; const dryRunSubmissionMock = jest.fn().mockResolvedValue({ valid: true, formName: FormNames.ExportFinancialReports, - data: { data: payload }, + data: { data: casSupplierMaintenanceUpdatesPayload }, }); formService.dryRunSubmission = dryRunSubmissionMock; // Act/Assert await request(app.getHttpServer()) .post(endpoint) - .send(payload) + .send(casSupplierMaintenanceUpdatesPayload) .auth(ministryUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.CREATED) .then((response) => { @@ -1519,29 +1519,22 @@ describe("ReportAestController(e2e)-exportReport", () => { sharedCASSupplierUpdatedStudent.sinValidation = newSINValidation; await db.student.save(sharedCASSupplierUpdatedStudent); - const casSupplierMaintenanceUpdates = - "CAS_Supplier_Maintenance_Updates_Report"; const endpoint = "/aest/report"; const ministryUserToken = await getAESTToken( AESTGroups.BusinessAdministrators, ); - // Payload with no parameters. - const payload = { - reportName: casSupplierMaintenanceUpdates, - params: {}, - }; const dryRunSubmissionMock = jest.fn().mockResolvedValue({ valid: true, formName: FormNames.ExportFinancialReports, - data: { data: payload }, + data: { data: casSupplierMaintenanceUpdatesPayload }, }); formService.dryRunSubmission = dryRunSubmissionMock; // Act/Assert await request(app.getHttpServer()) .post(endpoint) - .send(payload) + .send(casSupplierMaintenanceUpdatesPayload) .auth(ministryUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.CREATED) .then((response) => { @@ -1584,29 +1577,22 @@ describe("ReportAestController(e2e)-exportReport", () => { "Updated Address Line 1"; await db.student.save(sharedCASSupplierUpdatedStudent); - const casSupplierMaintenanceUpdates = - "CAS_Supplier_Maintenance_Updates_Report"; const endpoint = "/aest/report"; const ministryUserToken = await getAESTToken( AESTGroups.BusinessAdministrators, ); - // Payload with no parameters. - const payload = { - reportName: casSupplierMaintenanceUpdates, - params: {}, - }; const dryRunSubmissionMock = jest.fn().mockResolvedValue({ valid: true, formName: FormNames.ExportFinancialReports, - data: { data: payload }, + data: { data: casSupplierMaintenanceUpdatesPayload }, }); formService.dryRunSubmission = dryRunSubmissionMock; // Act/Assert await request(app.getHttpServer()) .post(endpoint) - .send(payload) + .send(casSupplierMaintenanceUpdatesPayload) .auth(ministryUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.CREATED) .then((response) => { @@ -1649,29 +1635,22 @@ describe("ReportAestController(e2e)-exportReport", () => { "Updated postal code"; await db.student.save(sharedCASSupplierUpdatedStudent); - const casSupplierMaintenanceUpdates = - "CAS_Supplier_Maintenance_Updates_Report"; const endpoint = "/aest/report"; const ministryUserToken = await getAESTToken( AESTGroups.BusinessAdministrators, ); - // Payload with no parameters. - const payload = { - reportName: casSupplierMaintenanceUpdates, - params: {}, - }; const dryRunSubmissionMock = jest.fn().mockResolvedValue({ valid: true, formName: FormNames.ExportFinancialReports, - data: { data: payload }, + data: { data: casSupplierMaintenanceUpdatesPayload }, }); formService.dryRunSubmission = dryRunSubmissionMock; // Act/Assert await request(app.getHttpServer()) .post(endpoint) - .send(payload) + .send(casSupplierMaintenanceUpdatesPayload) .auth(ministryUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.CREATED) .then((response) => { @@ -1714,29 +1693,22 @@ describe("ReportAestController(e2e)-exportReport", () => { sharedCASSupplierUpdatedStudent.user.lastName.toUpperCase(); await db.student.save(sharedCASSupplierUpdatedStudent); - const casSupplierMaintenanceUpdates = - "CAS_Supplier_Maintenance_Updates_Report"; const endpoint = "/aest/report"; const ministryUserToken = await getAESTToken( AESTGroups.BusinessAdministrators, ); - // Payload with no parameters. - const payload = { - reportName: casSupplierMaintenanceUpdates, - params: {}, - }; const dryRunSubmissionMock = jest.fn().mockResolvedValue({ valid: true, formName: FormNames.ExportFinancialReports, - data: { data: payload }, + data: { data: casSupplierMaintenanceUpdatesPayload }, }); formService.dryRunSubmission = dryRunSubmissionMock; // Act/Assert await request(app.getHttpServer()) .post(endpoint) - .send(payload) + .send(casSupplierMaintenanceUpdatesPayload) .auth(ministryUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.CREATED) .then((response) => { diff --git a/sources/packages/web/src/constants/report-constants.ts b/sources/packages/web/src/constants/report-constants.ts index eb5442889a..4f36195e75 100644 --- a/sources/packages/web/src/constants/report-constants.ts +++ b/sources/packages/web/src/constants/report-constants.ts @@ -10,6 +10,10 @@ export const INSTITUTION_REPORTS: OptionItemAPIOutDTO[] = [ ]; export const MINISTRY_REPORTS: OptionItemAPIOutDTO[] = [ + { + description: "CAS Supplier Maintenance Updates", + id: "CAS_Supplier_Maintenance_Updates_Report", + }, { description: "Data Inventory", id: "Data_Inventory_Report" }, { description: "Disbursements", id: "Disbursement_Report" }, { @@ -33,8 +37,4 @@ export const MINISTRY_REPORTS: OptionItemAPIOutDTO[] = [ description: "Student Unmet Need", id: "Ministry_Student_Unmet_Need_Report", }, - { - description: "CAS Supplier Maintenance Updates", - id: "CAS_Supplier_Maintenance_Updates_Report", - }, ];