From e03b2631938bad941ec052eb38278bc4297e23c0 Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Fri, 12 Jun 2026 09:05:03 -0700 Subject: [PATCH 1/9] Add PIR outcome to applications --- .../application.aest.controller.ts | 6 ++- .../application.controller.service.ts | 30 ++++++++++++ .../application.institutions.controller.ts | 8 +++- .../application.students.controller.ts | 5 ++ .../application/models/application.dto.ts | 14 ++++++ .../application/application.service.ts | 25 ++++++++++ .../src/form-definitions/sfaa2022-23.json | 46 +++++++++++++++++++ .../src/form-definitions/sfaa2023-24.json | 46 +++++++++++++++++++ .../src/form-definitions/sfaa2024-25.json | 46 +++++++++++++++++++ .../src/form-definitions/sfaa2025-26-ft.json | 46 +++++++++++++++++++ .../src/form-definitions/sfaa2025-26-pt.json | 46 +++++++++++++++++++ .../src/form-definitions/sfaa2026-27-ft.json | 46 +++++++++++++++++++ .../src/form-definitions/sfaa2026-27-pt.json | 46 +++++++++++++++++++ 13 files changed, 408 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts index aaab15699c..43e6dc2fe1 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts @@ -82,7 +82,7 @@ export class ApplicationAESTController extends BaseController { ): Promise { const application = await this.applicationService.getApplicationById( applicationId, - { loadDynamicData }, + { loadDynamicData, loadPIRSummaryData: true }, ); if (!application) { throw new NotFoundException( @@ -113,6 +113,10 @@ export class ApplicationAESTController extends BaseController { currentReadOnlyDataPromise, previousReadOnlyDataPromise, ]); + this.applicationControllerService.addPIRSummaryToFormData( + application, + currentReadOnlyData, + ); application.data = currentReadOnlyData; } return this.applicationControllerService.transformToApplicationDTO( diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts index d123627e8e..56c28ae472 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts @@ -68,7 +68,9 @@ import { StudentAppealStatus, ApplicationEditStatusInProgress, APPLICATION_EDIT_STATUS_IN_PROGRESS_VALUES, + AssessmentTriggerType, DynamicFormType, + ProgramInfoStatus, StudentScholasticStandingChangeType, } from "@sims/sims-db"; import { ApiProcessError } from "../../types"; @@ -106,6 +108,34 @@ export class ApplicationControllerService { private readonly userService: UserService, ) {} + /** + * Enriches the application form data with PIR outcome summary fields when the PIR + * has been completed. The summary fields are sourced from the original assessment + * offering so they always reflect the state at the time PIR was completed, regardless + * of subsequent reassessments. + * @param application application entity loaded with student assessments, offering, and + * education program data (requires loadPIRSummaryData option when calling getApplicationById). + * @param formData form data object to be enriched with PIR summary fields. + */ + addPIRSummaryToFormData( + application: Application, + formData: ApplicationFormData, + ): void { + if (application.pirStatus !== ProgramInfoStatus.completed) { + return; + } + const originalAssessment = application.studentAssessments?.find( + (assessment) => + assessment.triggerType === AssessmentTriggerType.OriginalAssessment, + ); + const offering = originalAssessment?.offering; + if (!offering) { + return; + } + formData.pirSummaryProgramName = offering.educationProgram?.name; + formData.pirSummaryOfferingName = getOfferingNameAndPeriod(offering); + } + /** * Add location, program and offering labels * and reset the dropdown value for non diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts index 368a57f4ff..1aeacf2b06 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts @@ -70,13 +70,19 @@ export class ApplicationInstitutionsController extends BaseController { { loadDynamicData, studentId: studentId, + loadPIRSummaryData: true, }, ); if (loadDynamicData) { - application.data = + const applicationFormData = await this.applicationControllerService.generateApplicationFormData( application.data, ); + this.applicationControllerService.addPIRSummaryToFormData( + application, + applicationFormData, + ); + application.data = applicationFormData; } return this.applicationControllerService.transformToApplicationDTO( application, diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts index 7d1b8d1d50..85700bcb3c 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts @@ -108,6 +108,7 @@ export class ApplicationStudentsController extends BaseController { { loadDynamicData: true, studentId: userToken.studentId, + loadPIRSummaryData: true, }, ); if (!application) { @@ -134,6 +135,10 @@ export class ApplicationStudentsController extends BaseController { hasPreviouslyCompletedPIRPromise, ]); + this.applicationControllerService.addPIRSummaryToFormData( + application, + applicationData, + ); application.data = applicationData; return this.applicationControllerService.transformToApplicationDetailForStudentDTO( application, diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts b/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts index 587e8465f6..3b13f62a6a 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts @@ -102,6 +102,20 @@ export interface ApplicationFormData extends ApplicationData { id?: number; name?: string; }; + + /** + * Program name from the completed PIR's original assessment offering. + * Injected for the read-only PIR outcome summary section in the application form. + * Only populated when pirStatus is completed. + */ + pirSummaryProgramName?: string; + + /** + * Offering name (with year of study and dates) from the completed PIR's original assessment offering. + * Injected for the read-only PIR outcome summary section in the application form. + * Only populated when pirStatus is completed. + */ + pirSummaryOfferingName?: string; } class SupportingUserParentAPIOutDTO { diff --git a/sources/packages/backend/apps/api/src/services/application/application.service.ts b/sources/packages/backend/apps/api/src/services/application/application.service.ts index 9746bf0b12..d380c92601 100644 --- a/sources/packages/backend/apps/api/src/services/application/application.service.ts +++ b/sources/packages/backend/apps/api/src/services/application/application.service.ts @@ -947,6 +947,11 @@ export class ApplicationService extends RecordDataModelService { loadDynamicData?: boolean; studentId?: number; entityManager?: EntityManager; + /** + * When true, loads the student assessments with offering and program details + * needed to display the PIR outcome summary. + */ + loadPIRSummaryData?: boolean; }, ): Promise { const applicationRepo = @@ -1015,6 +1020,23 @@ export class ApplicationService extends RecordDataModelService { lastName: true, }, }, + ...(options?.loadPIRSummaryData && { + studentAssessments: { + id: true, + triggerType: true, + offering: { + id: true, + name: true, + studyStartDate: true, + studyEndDate: true, + yearOfStudy: true, + educationProgram: { + id: true, + name: true, + }, + }, + }, + }), }, relations: { applicationException: true, @@ -1026,6 +1048,9 @@ export class ApplicationService extends RecordDataModelService { precedingApplication: { currentAssessment: { offering: true, studentAppeal: true }, }, + ...(options?.loadPIRSummaryData && { + studentAssessments: { offering: { educationProgram: true } }, + }), }, where: { id: applicationId, diff --git a/sources/packages/forms/src/form-definitions/sfaa2022-23.json b/sources/packages/forms/src/form-definitions/sfaa2022-23.json index 6055167bf2..edf8ba9229 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2022-23.json +++ b/sources/packages/forms/src/form-definitions/sfaa2022-23.json @@ -885,6 +885,52 @@ }, "lockKey": true }, + { + "key": "pirSummaryPanel", + "label": "Program information request summary", + "input": false, + "customConditional": "show = !!data.pirSummaryProgramName;", + "type": "panel", + "hideLabel": true, + "tableView": false, + "collapsible": false, + "components": [ + { + "label": "HTML", + "tag": "h4", + "className": "category-header-medium-small", + "attrs": [{ "attr": "", "value": "" }], + "content": "Program information request summary", + "refreshOnChange": false, + "key": "pirSummaryHeader", + "type": "htmlelement", + "input": false, + "tableView": false + }, + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryProgramName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryOfferingName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] + }, { "key": "programStudyperiodwarning", "label": "Study period end date warning", diff --git a/sources/packages/forms/src/form-definitions/sfaa2023-24.json b/sources/packages/forms/src/form-definitions/sfaa2023-24.json index e71c804185..0107f710d3 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2023-24.json +++ b/sources/packages/forms/src/form-definitions/sfaa2023-24.json @@ -885,6 +885,52 @@ }, "lockKey": true }, + { + "key": "pirSummaryPanel", + "label": "Program information request summary", + "input": false, + "customConditional": "show = !!data.pirSummaryProgramName;", + "type": "panel", + "hideLabel": true, + "tableView": false, + "collapsible": false, + "components": [ + { + "label": "HTML", + "tag": "h4", + "className": "category-header-medium-small", + "attrs": [{ "attr": "", "value": "" }], + "content": "Program information request summary", + "refreshOnChange": false, + "key": "pirSummaryHeader", + "type": "htmlelement", + "input": false, + "tableView": false + }, + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryProgramName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryOfferingName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] + }, { "key": "programStudyperiodwarning", "label": "Study period end date warning", diff --git a/sources/packages/forms/src/form-definitions/sfaa2024-25.json b/sources/packages/forms/src/form-definitions/sfaa2024-25.json index 77b40f1648..f322d57477 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2024-25.json +++ b/sources/packages/forms/src/form-definitions/sfaa2024-25.json @@ -891,6 +891,52 @@ }, "lockKey": true }, + { + "key": "pirSummaryPanel", + "label": "Program information request summary", + "input": false, + "customConditional": "show = !!data.pirSummaryProgramName;", + "type": "panel", + "hideLabel": true, + "tableView": false, + "collapsible": false, + "components": [ + { + "label": "HTML", + "tag": "h4", + "className": "category-header-medium-small", + "attrs": [{ "attr": "", "value": "" }], + "content": "Program information request summary", + "refreshOnChange": false, + "key": "pirSummaryHeader", + "type": "htmlelement", + "input": false, + "tableView": false + }, + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryProgramName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryOfferingName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] + }, { "key": "programStudyperiodwarning", "label": "Study period end date warning", diff --git a/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json b/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json index 44fab19db9..e11f30fb6f 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json +++ b/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json @@ -1204,6 +1204,52 @@ }, "lockKey": true }, + { + "key": "pirSummaryPanel", + "label": "Program information request summary", + "input": false, + "customConditional": "show = !!data.pirSummaryProgramName;", + "type": "panel", + "hideLabel": true, + "tableView": false, + "collapsible": false, + "components": [ + { + "label": "HTML", + "tag": "h4", + "className": "category-header-medium-small", + "attrs": [{ "attr": "", "value": "" }], + "content": "Program information request summary", + "refreshOnChange": false, + "key": "pirSummaryHeader", + "type": "htmlelement", + "input": false, + "tableView": false + }, + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryProgramName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryOfferingName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] + }, { "title": "PIR Resubmission", "customClass": "panel-border-none", diff --git a/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json b/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json index 1aeec75e07..c1cdc998d0 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json +++ b/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json @@ -1161,6 +1161,52 @@ }, "lockKey": true }, + { + "key": "pirSummaryPanel", + "label": "Program information request summary", + "input": false, + "customConditional": "show = !!data.pirSummaryProgramName;", + "type": "panel", + "hideLabel": true, + "tableView": false, + "collapsible": false, + "components": [ + { + "label": "HTML", + "tag": "h4", + "className": "category-header-medium-small", + "attrs": [{ "attr": "", "value": "" }], + "content": "Program information request summary", + "refreshOnChange": false, + "key": "pirSummaryHeader", + "type": "htmlelement", + "input": false, + "tableView": false + }, + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryProgramName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryOfferingName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] + }, { "title": "PIR Resubmission", "customClass": "panel-border-none", diff --git a/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json b/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json index b18077d69a..121cf1772d 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json +++ b/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json @@ -1204,6 +1204,52 @@ }, "lockKey": true }, + { + "key": "pirSummaryPanel", + "label": "Program information request summary", + "input": false, + "customConditional": "show = !!data.pirSummaryProgramName;", + "type": "panel", + "hideLabel": true, + "tableView": false, + "collapsible": false, + "components": [ + { + "label": "HTML", + "tag": "h4", + "className": "category-header-medium-small", + "attrs": [{ "attr": "", "value": "" }], + "content": "Program information request summary", + "refreshOnChange": false, + "key": "pirSummaryHeader", + "type": "htmlelement", + "input": false, + "tableView": false + }, + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryProgramName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryOfferingName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] + }, { "title": "PIR Resubmission", "customClass": "panel-border-none", diff --git a/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json b/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json index 1837acf081..2ba8d0e3b9 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json +++ b/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json @@ -1161,6 +1161,52 @@ }, "lockKey": true }, + { + "key": "pirSummaryPanel", + "label": "Program information request summary", + "input": false, + "customConditional": "show = !!data.pirSummaryProgramName;", + "type": "panel", + "hideLabel": true, + "tableView": false, + "collapsible": false, + "components": [ + { + "label": "HTML", + "tag": "h4", + "className": "category-header-medium-small", + "attrs": [{ "attr": "", "value": "" }], + "content": "Program information request summary", + "refreshOnChange": false, + "key": "pirSummaryHeader", + "type": "htmlelement", + "input": false, + "tableView": false + }, + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryProgramName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "pirSummaryOfferingName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] + }, { "title": "PIR Resubmission", "customClass": "panel-border-none", From 45975ec2c3d293267659cf4d2e8f7b64e56c0ffe Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Fri, 12 Jun 2026 09:31:25 -0700 Subject: [PATCH 2/9] Update e2e tests --- ...ntroller.getApplicationDetails.e2e-spec.ts | 76 ++++++++++++++++- ...ntroller.getApplicationDetails.e2e-spec.ts | 82 +++++++++++++++++- ...ents.controller.getApplication.e2e-spec.ts | 83 +++++++++++++++++++ 3 files changed, 239 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts index 2af87ca72f..fe4dbcd56e 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts @@ -17,8 +17,12 @@ import { ApplicationStatus, EducationProgramOffering, OfferingIntensity, + ProgramInfoStatus, } from "@sims/sims-db"; -import { getUserFullName } from "../../../../utilities"; +import { + getUserFullName, + getOfferingNameAndPeriod, +} from "../../../../utilities"; import { addDays, getISODateOnlyString } from "@sims/utilities"; describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { @@ -255,6 +259,76 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { ]); }); + it("Should return PIR summary fields in application data when the application has PIR status completed and dynamic data is loaded.", async () => { + // Arrange + const offeringInitialValues = { + name: "Test PIR Offering Name", + studyStartDate: "2024-09-01", + studyEndDate: "2025-04-30", + yearOfStudy: 2, + } as Partial; + const application = await saveFakeApplication( + db.dataSource, + {}, + { + applicationStatus: ApplicationStatus.Assessment, + offeringIntensity: OfferingIntensity.fullTime, + pirStatus: ProgramInfoStatus.completed, + offeringInitialValues, + }, + ); + // Load the offering with its education program to build expected values. + const savedOffering = await db.educationProgramOffering.findOne({ + where: { id: application.currentAssessment.offering.id }, + relations: { educationProgram: true }, + select: { + id: true, + name: true, + studyStartDate: true, + studyEndDate: true, + yearOfStudy: true, + educationProgram: { id: true, name: true }, + }, + }); + const token = await getAESTToken(AESTGroups.BusinessAdministrators); + const endpoint = `/aest/application/${application.id}`; + + // Act/Assert + const response = await request(app.getHttpServer()) + .get(endpoint) + .auth(token, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + expect(response.body.data.pirSummaryProgramName).toBe( + savedOffering.educationProgram.name, + ); + expect(response.body.data.pirSummaryOfferingName).toBe( + getOfferingNameAndPeriod(savedOffering), + ); + }); + + it("Should not return PIR summary fields in application data when the application does not have PIR status completed.", async () => { + // Arrange + const application = await saveFakeApplication( + db.dataSource, + {}, + { + applicationStatus: ApplicationStatus.Assessment, + offeringIntensity: OfferingIntensity.fullTime, + pirStatus: ProgramInfoStatus.notRequired, + }, + ); + const token = await getAESTToken(AESTGroups.BusinessAdministrators); + const endpoint = `/aest/application/${application.id}`; + + // Act/Assert + const response = await request(app.getHttpServer()) + .get(endpoint) + .auth(token, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + expect(response.body.data.pirSummaryProgramName).toBeUndefined(); + expect(response.body.data.pirSummaryOfferingName).toBeUndefined(); + }); + afterAll(async () => { await app?.close(); }); diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts index be021bb73f..318954d338 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts @@ -22,9 +22,13 @@ import { EducationProgramOffering, InstitutionLocation, OfferingIntensity, + ProgramInfoStatus, } from "@sims/sims-db"; import { addDays, getISODateOnlyString } from "@sims/utilities"; -import { getUserFullName } from "../../../../utilities"; +import { + getUserFullName, + getOfferingNameAndPeriod, +} from "../../../../utilities"; describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { let app: INestApplication; @@ -275,6 +279,82 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { ); }); + it("Should return PIR summary fields in application data when the application has PIR status completed.", async () => { + // Arrange + const offeringInitialValues = { + name: "Test PIR Offering Name", + studyStartDate: "2024-09-01", + studyEndDate: "2025-04-30", + yearOfStudy: 2, + } as Partial; + const savedApplication = await saveFakeApplication( + db.dataSource, + { institutionLocation: collegeFLocation }, + { + applicationStatus: ApplicationStatus.Assessment, + offeringIntensity: OfferingIntensity.fullTime, + pirStatus: ProgramInfoStatus.completed, + offeringInitialValues, + }, + ); + // Load the offering with its education program to build expected values. + const savedOffering = await db.educationProgramOffering.findOne({ + where: { id: savedApplication.currentAssessment.offering.id }, + relations: { educationProgram: true }, + select: { + id: true, + name: true, + studyStartDate: true, + studyEndDate: true, + yearOfStudy: true, + educationProgram: { id: true, name: true }, + }, + }); + const student = savedApplication.student; + const endpoint = `/institutions/application/student/${student.id}/application/${savedApplication.id}`; + const institutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeFUser, + ); + + // Act/Assert + const response = await request(app.getHttpServer()) + .get(endpoint) + .auth(institutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + expect(response.body.data.pirSummaryProgramName).toBe( + savedOffering.educationProgram.name, + ); + expect(response.body.data.pirSummaryOfferingName).toBe( + getOfferingNameAndPeriod(savedOffering), + ); + }); + + it("Should not return PIR summary fields in application data when the application does not have PIR status completed.", async () => { + // Arrange + const savedApplication = await saveFakeApplication( + db.dataSource, + { institutionLocation: collegeFLocation }, + { + applicationStatus: ApplicationStatus.Assessment, + offeringIntensity: OfferingIntensity.fullTime, + pirStatus: ProgramInfoStatus.notRequired, + }, + ); + const student = savedApplication.student; + const endpoint = `/institutions/application/student/${student.id}/application/${savedApplication.id}`; + const institutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeFUser, + ); + + // Act/Assert + const response = await request(app.getHttpServer()) + .get(endpoint) + .auth(institutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + expect(response.body.data.pirSummaryProgramName).toBeUndefined(); + expect(response.body.data.pirSummaryOfferingName).toBeUndefined(); + }); + afterAll(async () => { await app?.close(); }); diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts index d312c9539f..2f3f9f1d5c 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts @@ -17,10 +17,12 @@ import { import { Application, ApplicationStatus, + EducationProgramOffering, OfferingIntensity, ProgramInfoStatus, Student, } from "@sims/sims-db"; +import { getOfferingNameAndPeriod } from "../../../../utilities"; import { getDateOnlyFormat } from "@sims/utilities"; import { TestingModule } from "@nestjs/testing"; @@ -203,6 +205,11 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { programName: "My Program", workflowName: "", programDescription: "This is my program.", + pirSummaryProgramName: + application.currentAssessment?.offering?.educationProgram.name, + pirSummaryOfferingName: getOfferingNameAndPeriod( + application.currentAssessment?.offering, + ), }, applicationStatus: application.applicationStatus, applicationEditStatus: application.applicationEditStatus, @@ -308,6 +315,82 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { }); }); + it("Should return PIR summary fields in application data when the application has PIR status completed.", async () => { + // Arrange + const offeringInitialValues = { + name: "Test PIR Offering Name", + studyStartDate: "2024-09-01", + studyEndDate: "2025-04-30", + yearOfStudy: 2, + } as Partial; + const application = await saveFakeApplication( + db.dataSource, + { student }, + { + applicationStatus: ApplicationStatus.Assessment, + offeringIntensity: OfferingIntensity.fullTime, + pirStatus: ProgramInfoStatus.completed, + offeringInitialValues, + }, + ); + // Load the offering with its education program to build expected values. + const savedOffering = await db.educationProgramOffering.findOne({ + where: { id: application.currentAssessment.offering.id }, + relations: { educationProgram: true }, + select: { + id: true, + name: true, + studyStartDate: true, + studyEndDate: true, + yearOfStudy: true, + educationProgram: { id: true, name: true }, + }, + }); + const endpoint = `/students/application/${application.id}`; + const token = await getStudentToken( + FakeStudentUsersTypes.FakeStudentUserType1, + ); + await mockJWTUserInfo(appModule, application.student.user); + + // Act/Assert + const response = await request(app.getHttpServer()) + .get(endpoint) + .auth(token, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + expect(response.body.data.pirSummaryProgramName).toBe( + savedOffering.educationProgram.name, + ); + expect(response.body.data.pirSummaryOfferingName).toBe( + getOfferingNameAndPeriod(savedOffering), + ); + }); + + it("Should not return PIR summary fields in application data when the application does not have PIR status completed.", async () => { + // Arrange + const application = await saveFakeApplication( + db.dataSource, + { student }, + { + applicationStatus: ApplicationStatus.Assessment, + offeringIntensity: OfferingIntensity.fullTime, + pirStatus: ProgramInfoStatus.notRequired, + }, + ); + const endpoint = `/students/application/${application.id}`; + const token = await getStudentToken( + FakeStudentUsersTypes.FakeStudentUserType1, + ); + await mockJWTUserInfo(appModule, application.student.user); + + // Act/Assert + const response = await request(app.getHttpServer()) + .get(endpoint) + .auth(token, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + expect(response.body.data.pirSummaryProgramName).toBeUndefined(); + expect(response.body.data.pirSummaryOfferingName).toBeUndefined(); + }); + afterAll(async () => { await app?.close(); }); From 9e0447a688b08f11297a0f8f2bbee3a897fd2e5f Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Fri, 12 Jun 2026 10:40:33 -0700 Subject: [PATCH 3/9] Update application.service.ts --- .../apps/api/src/services/application/application.service.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sources/packages/backend/apps/api/src/services/application/application.service.ts b/sources/packages/backend/apps/api/src/services/application/application.service.ts index d380c92601..20465c503a 100644 --- a/sources/packages/backend/apps/api/src/services/application/application.service.ts +++ b/sources/packages/backend/apps/api/src/services/application/application.service.ts @@ -939,6 +939,7 @@ export class ApplicationService extends RecordDataModelService { * - `studentId` student id. * - `entityManager` entity manager to be used for the query. Useful when * it needs to be executed in a transaction. + * - `loadPIRSummaryData` indicates if the data needed for the PIR outcome summary should be loaded. * @returns student application. */ async getApplicationById( @@ -947,10 +948,6 @@ export class ApplicationService extends RecordDataModelService { loadDynamicData?: boolean; studentId?: number; entityManager?: EntityManager; - /** - * When true, loads the student assessments with offering and program details - * needed to display the PIR outcome summary. - */ loadPIRSummaryData?: boolean; }, ): Promise { From 8823b81f39ad6057e618e3b787607e0caca2db0a Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Mon, 15 Jun 2026 10:35:00 -0700 Subject: [PATCH 4/9] Updated PR Comments --- ...ntroller.getApplicationDetails.e2e-spec.ts | 46 ++++++------- ...ntroller.getApplicationDetails.e2e-spec.ts | 47 ++++++-------- ...ents.controller.getApplication.e2e-spec.ts | 45 +++++-------- .../application.aest.controller.ts | 7 +- .../application.controller.service.ts | 65 ++++++++++--------- .../application.institutions.controller.ts | 9 +-- .../application.students.controller.ts | 6 +- .../application/models/application.dto.ts | 24 +++---- .../application/application.service.ts | 40 +++++------- .../src/form-definitions/sfaa2022-23.json | 49 ++++++++------ .../src/form-definitions/sfaa2023-24.json | 49 ++++++++------ .../src/form-definitions/sfaa2024-25.json | 49 ++++++++------ .../src/form-definitions/sfaa2025-26-ft.json | 49 ++++++++------ .../src/form-definitions/sfaa2025-26-pt.json | 49 ++++++++------ .../src/form-definitions/sfaa2026-27-ft.json | 49 ++++++++------ .../src/form-definitions/sfaa2026-27-pt.json | 49 ++++++++------ 16 files changed, 334 insertions(+), 298 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts index fe4dbcd56e..a29f33b332 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts @@ -259,7 +259,7 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { ]); }); - it("Should return PIR summary fields in application data when the application has PIR status completed and dynamic data is loaded.", async () => { + it("Should return PIR summary in application data when the application has PIR status completed and dynamic data is loaded.", async () => { // Arrange const offeringInitialValues = { name: "Test PIR Offering Name", @@ -277,36 +277,29 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { offeringInitialValues, }, ); - // Load the offering with its education program to build expected values. - const savedOffering = await db.educationProgramOffering.findOne({ - where: { id: application.currentAssessment.offering.id }, - relations: { educationProgram: true }, - select: { - id: true, - name: true, - studyStartDate: true, - studyEndDate: true, - yearOfStudy: true, - educationProgram: { id: true, name: true }, - }, - }); + + const savedOffering = application.currentAssessment!.offering!; const token = await getAESTToken(AESTGroups.BusinessAdministrators); const endpoint = `/aest/application/${application.id}`; // Act/Assert - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .get(endpoint) .auth(token, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - expect(response.body.data.pirSummaryProgramName).toBe( - savedOffering.educationProgram.name, - ); - expect(response.body.data.pirSummaryOfferingName).toBe( - getOfferingNameAndPeriod(savedOffering), - ); + .expect(HttpStatus.OK) + .expect(({ body }) => + expect(body).toMatchObject({ + data: { + pirSummary: { + programName: savedOffering.educationProgram.name, + offeringName: getOfferingNameAndPeriod(savedOffering), + }, + }, + }), + ); }); - it("Should not return PIR summary fields in application data when the application does not have PIR status completed.", async () => { + it("Should not return PIR summary in application data when the application does not have PIR status completed.", async () => { // Arrange const application = await saveFakeApplication( db.dataSource, @@ -321,12 +314,11 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { const endpoint = `/aest/application/${application.id}`; // Act/Assert - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .get(endpoint) .auth(token, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - expect(response.body.data.pirSummaryProgramName).toBeUndefined(); - expect(response.body.data.pirSummaryOfferingName).toBeUndefined(); + .expect(HttpStatus.OK) + .expect(({ body }) => expect(body.data.pirSummary).toBeUndefined()); }); afterAll(async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts index 318954d338..ad0a8ae19a 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts @@ -279,7 +279,7 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { ); }); - it("Should return PIR summary fields in application data when the application has PIR status completed.", async () => { + it("Should return PIR summary in application data when the application has PIR status completed.", async () => { // Arrange const offeringInitialValues = { name: "Test PIR Offering Name", @@ -297,19 +297,7 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { offeringInitialValues, }, ); - // Load the offering with its education program to build expected values. - const savedOffering = await db.educationProgramOffering.findOne({ - where: { id: savedApplication.currentAssessment.offering.id }, - relations: { educationProgram: true }, - select: { - id: true, - name: true, - studyStartDate: true, - studyEndDate: true, - yearOfStudy: true, - educationProgram: { id: true, name: true }, - }, - }); + const savedOffering = savedApplication.currentAssessment!.offering!; const student = savedApplication.student; const endpoint = `/institutions/application/student/${student.id}/application/${savedApplication.id}`; const institutionUserToken = await getInstitutionToken( @@ -317,19 +305,23 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { ); // Act/Assert - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .get(endpoint) .auth(institutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - expect(response.body.data.pirSummaryProgramName).toBe( - savedOffering.educationProgram.name, - ); - expect(response.body.data.pirSummaryOfferingName).toBe( - getOfferingNameAndPeriod(savedOffering), - ); + .expect(HttpStatus.OK) + .expect(({ body }) => + expect(body).toMatchObject({ + data: { + pirSummary: { + programName: savedOffering!.educationProgram.name, + offeringName: getOfferingNameAndPeriod(savedOffering!), + }, + }, + }), + ); }); - it("Should not return PIR summary fields in application data when the application does not have PIR status completed.", async () => { + it("Should not return PIR summary in application data when the application does not have PIR status completed.", async () => { // Arrange const savedApplication = await saveFakeApplication( db.dataSource, @@ -347,12 +339,13 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { ); // Act/Assert - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .get(endpoint) .auth(institutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - expect(response.body.data.pirSummaryProgramName).toBeUndefined(); - expect(response.body.data.pirSummaryOfferingName).toBeUndefined(); + .expect(HttpStatus.OK) + .expect(({ body }) => { + expect(body.data.pirSummary).toBeUndefined(); + }); }); afterAll(async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts index 2f3f9f1d5c..30a266aa4e 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts @@ -315,7 +315,7 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { }); }); - it("Should return PIR summary fields in application data when the application has PIR status completed.", async () => { + it("Should return PIR summary in application data when the application has PIR status completed.", async () => { // Arrange const offeringInitialValues = { name: "Test PIR Offering Name", @@ -333,19 +333,7 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { offeringInitialValues, }, ); - // Load the offering with its education program to build expected values. - const savedOffering = await db.educationProgramOffering.findOne({ - where: { id: application.currentAssessment.offering.id }, - relations: { educationProgram: true }, - select: { - id: true, - name: true, - studyStartDate: true, - studyEndDate: true, - yearOfStudy: true, - educationProgram: { id: true, name: true }, - }, - }); + const savedOffering = application.currentAssessment!.offering!; const endpoint = `/students/application/${application.id}`; const token = await getStudentToken( FakeStudentUsersTypes.FakeStudentUserType1, @@ -353,19 +341,23 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { await mockJWTUserInfo(appModule, application.student.user); // Act/Assert - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .get(endpoint) .auth(token, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - expect(response.body.data.pirSummaryProgramName).toBe( - savedOffering.educationProgram.name, - ); - expect(response.body.data.pirSummaryOfferingName).toBe( - getOfferingNameAndPeriod(savedOffering), - ); + .expect(HttpStatus.OK) + .expect(({ body }) => + expect(body).toMatchObject({ + data: { + pirSummary: { + programName: savedOffering.educationProgram.name, + offeringName: getOfferingNameAndPeriod(savedOffering), + }, + }, + }), + ); }); - it("Should not return PIR summary fields in application data when the application does not have PIR status completed.", async () => { + it("Should not return PIR summary in application data when the application does not have PIR status completed.", async () => { // Arrange const application = await saveFakeApplication( db.dataSource, @@ -383,12 +375,11 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { await mockJWTUserInfo(appModule, application.student.user); // Act/Assert - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .get(endpoint) .auth(token, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - expect(response.body.data.pirSummaryProgramName).toBeUndefined(); - expect(response.body.data.pirSummaryOfferingName).toBeUndefined(); + .expect(HttpStatus.OK) + .expect(({ body }) => expect(body.data.pirSummary).toBeUndefined()); }); afterAll(async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts index 43e6dc2fe1..e05238d0a0 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts @@ -82,7 +82,7 @@ export class ApplicationAESTController extends BaseController { ): Promise { const application = await this.applicationService.getApplicationById( applicationId, - { loadDynamicData, loadPIRSummaryData: true }, + { loadDynamicData }, ); if (!application) { throw new NotFoundException( @@ -101,6 +101,7 @@ export class ApplicationAESTController extends BaseController { const currentReadOnlyDataPromise = this.applicationControllerService.generateApplicationFormData( application.data, + application, ); // If there is a previous application, generate its read-only data. const previousReadOnlyDataPromise = @@ -113,10 +114,6 @@ export class ApplicationAESTController extends BaseController { currentReadOnlyDataPromise, previousReadOnlyDataPromise, ]); - this.applicationControllerService.addPIRSummaryToFormData( - application, - currentReadOnlyData, - ); application.data = currentReadOnlyData; } return this.applicationControllerService.transformToApplicationDTO( diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts index 56c28ae472..9bea0e2fc0 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts @@ -68,7 +68,6 @@ import { StudentAppealStatus, ApplicationEditStatusInProgress, APPLICATION_EDIT_STATUS_IN_PROGRESS_VALUES, - AssessmentTriggerType, DynamicFormType, ProgramInfoStatus, StudentScholasticStandingChangeType, @@ -108,52 +107,54 @@ export class ApplicationControllerService { private readonly userService: UserService, ) {} - /** - * Enriches the application form data with PIR outcome summary fields when the PIR - * has been completed. The summary fields are sourced from the original assessment - * offering so they always reflect the state at the time PIR was completed, regardless - * of subsequent reassessments. - * @param application application entity loaded with student assessments, offering, and - * education program data (requires loadPIRSummaryData option when calling getApplicationById). - * @param formData form data object to be enriched with PIR summary fields. - */ - addPIRSummaryToFormData( - application: Application, - formData: ApplicationFormData, - ): void { - if (application.pirStatus !== ProgramInfoStatus.completed) { - return; - } - const originalAssessment = application.studentAssessments?.find( - (assessment) => - assessment.triggerType === AssessmentTriggerType.OriginalAssessment, - ); - const offering = originalAssessment?.offering; - if (!offering) { - return; - } - formData.pirSummaryProgramName = offering.educationProgram?.name; - formData.pirSummaryOfferingName = getOfferingNameAndPeriod(offering); - } - /** * Add location, program and offering labels * and reset the dropdown value for non * designated location and not approved - * programs. - * @param data application data - * @returns ApplicationFormData + * programs. When application is provided, also enriches the form data + * with the PIR outcome summary when the PIR has been completed. + * @param data application data. + * @param application application entity. When provided, the PIR outcome + * summary is added to the form data if the PIR status is completed. + * @returns ApplicationFormData. */ async generateApplicationFormData( data: ApplicationData, + application?: Application, ): Promise { const additionalFormData = {} as ApplicationFormData; await this.processSelectedLocation(data, additionalFormData); await this.processSelectedProgram(data, additionalFormData); await this.processSelectedOffering(data, additionalFormData); + this.processPIRSummary(application, additionalFormData); return { ...data, ...additionalFormData }; } + /** + * Enriches the application form data with the PIR outcome summary when the PIR + * has been completed. The summary data is sourced from the current assessment + * offering which holds the PIR-approved program and offering information. + * @param application application entity loaded with current assessment offering + * and education program data. + * @param formData form data object to be enriched with the PIR summary. + */ + private processPIRSummary( + application: Application | undefined, + formData: ApplicationFormData, + ): void { + if (application?.pirStatus !== ProgramInfoStatus.completed) { + return; + } + const offering = application.currentAssessment?.offering; + if (!offering) { + return; + } + formData.pirSummary = { + programName: offering.educationProgram.name, + offeringName: getOfferingNameAndPeriod(offering), + }; + } + /** * Get status of all requests and confirmations in student application (Exception, PIR and COE). * @param applicationId application id. diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts index 1aeacf2b06..926e4809e5 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts @@ -70,19 +70,14 @@ export class ApplicationInstitutionsController extends BaseController { { loadDynamicData, studentId: studentId, - loadPIRSummaryData: true, }, ); if (loadDynamicData) { - const applicationFormData = + application.data = await this.applicationControllerService.generateApplicationFormData( application.data, + application, ); - this.applicationControllerService.addPIRSummaryToFormData( - application, - applicationFormData, - ); - application.data = applicationFormData; } return this.applicationControllerService.transformToApplicationDTO( application, diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts index 85700bcb3c..0bd305441b 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts @@ -108,7 +108,6 @@ export class ApplicationStudentsController extends BaseController { { loadDynamicData: true, studentId: userToken.studentId, - loadPIRSummaryData: true, }, ); if (!application) { @@ -120,6 +119,7 @@ export class ApplicationStudentsController extends BaseController { const applicationDataPromise = this.applicationControllerService.generateApplicationFormData( application.data, + application, ); const firstCOEPromise = this.confirmationOfEnrollmentService.getFirstDisbursementScheduleByApplication( @@ -135,10 +135,6 @@ export class ApplicationStudentsController extends BaseController { hasPreviouslyCompletedPIRPromise, ]); - this.applicationControllerService.addPIRSummaryToFormData( - application, - applicationData, - ); application.data = applicationData; return this.applicationControllerService.transformToApplicationDetailForStudentDTO( application, diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts b/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts index 3b13f62a6a..8dea032a65 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts @@ -104,18 +104,20 @@ export interface ApplicationFormData extends ApplicationData { }; /** - * Program name from the completed PIR's original assessment offering. - * Injected for the read-only PIR outcome summary section in the application form. - * Only populated when pirStatus is completed. + * PIR outcome summary data injected for the read-only PIR summary section + * in the application form. Only populated when pirStatus is completed. */ - pirSummaryProgramName?: string; - - /** - * Offering name (with year of study and dates) from the completed PIR's original assessment offering. - * Injected for the read-only PIR outcome summary section in the application form. - * Only populated when pirStatus is completed. - */ - pirSummaryOfferingName?: string; + pirSummary?: { + /** + * Program name from the completed PIR's current assessment offering. + */ + programName?: string; + /** + * Formatted offering name (including year of study and dates) from the + * completed PIR's current assessment offering. + */ + offeringName?: string; + }; } class SupportingUserParentAPIOutDTO { diff --git a/sources/packages/backend/apps/api/src/services/application/application.service.ts b/sources/packages/backend/apps/api/src/services/application/application.service.ts index 20465c503a..54d665b47c 100644 --- a/sources/packages/backend/apps/api/src/services/application/application.service.ts +++ b/sources/packages/backend/apps/api/src/services/application/application.service.ts @@ -935,11 +935,12 @@ export class ApplicationService extends RecordDataModelService { * Student id/ institution id can be provided for authorization purposes. * @param applicationId application id. * @param options object that should contain: - * - `loadDynamicData` indicates if the dynamic data(JSONB) should be loaded. + * - `loadDynamicData` indicates if the dynamic data(JSONB) should be loaded. When true, + * also loads the additional offering details (name, year of study, and education program) + * needed to display the PIR outcome summary. * - `studentId` student id. * - `entityManager` entity manager to be used for the query. Useful when * it needs to be executed in a transaction. - * - `loadPIRSummaryData` indicates if the data needed for the PIR outcome summary should be loaded. * @returns student application. */ async getApplicationById( @@ -948,7 +949,6 @@ export class ApplicationService extends RecordDataModelService { loadDynamicData?: boolean; studentId?: number; entityManager?: EntityManager; - loadPIRSummaryData?: boolean; }, ): Promise { const applicationRepo = @@ -989,6 +989,14 @@ export class ApplicationService extends RecordDataModelService { studyStartDate: true, studyEndDate: true, offeringStatus: true, + ...(options?.loadDynamicData && { + name: true, + yearOfStudy: true, + educationProgram: { + id: true, + name: true, + }, + }), }, }, location: { @@ -1017,27 +1025,14 @@ export class ApplicationService extends RecordDataModelService { lastName: true, }, }, - ...(options?.loadPIRSummaryData && { - studentAssessments: { - id: true, - triggerType: true, - offering: { - id: true, - name: true, - studyStartDate: true, - studyEndDate: true, - yearOfStudy: true, - educationProgram: { - id: true, - name: true, - }, - }, - }, - }), }, relations: { applicationException: true, - currentAssessment: { offering: true }, + currentAssessment: { + offering: options?.loadDynamicData + ? { educationProgram: true } + : true, + }, location: { institution: true }, pirDeniedReasonId: true, programYear: true, @@ -1045,9 +1040,6 @@ export class ApplicationService extends RecordDataModelService { precedingApplication: { currentAssessment: { offering: true, studentAppeal: true }, }, - ...(options?.loadPIRSummaryData && { - studentAssessments: { offering: { educationProgram: true } }, - }), }, where: { id: applicationId, diff --git a/sources/packages/forms/src/form-definitions/sfaa2022-23.json b/sources/packages/forms/src/form-definitions/sfaa2022-23.json index edf8ba9229..a1738aeda2 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2022-23.json +++ b/sources/packages/forms/src/form-definitions/sfaa2022-23.json @@ -889,7 +889,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!data.pirSummaryProgramName;", + "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", "type": "panel", "hideLabel": true, "tableView": false, @@ -908,26 +908,37 @@ "tableView": false }, { - "label": "Program name", - "disabled": true, - "tableView": true, - "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryProgramName", - "type": "textfield", + "type": "container", + "key": "pirSummary", "input": true, - "lockKey": true - }, - { - "label": "Offering name", - "disabled": true, - "tableView": true, + "tableView": false, + "label": "PIR Summary", + "hideLabel": true, "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryOfferingName", - "type": "textfield", - "input": true, - "lockKey": true + "components": [ + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "programName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "offeringName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] } ] }, diff --git a/sources/packages/forms/src/form-definitions/sfaa2023-24.json b/sources/packages/forms/src/form-definitions/sfaa2023-24.json index 0107f710d3..4a57fffd54 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2023-24.json +++ b/sources/packages/forms/src/form-definitions/sfaa2023-24.json @@ -889,7 +889,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!data.pirSummaryProgramName;", + "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", "type": "panel", "hideLabel": true, "tableView": false, @@ -908,26 +908,37 @@ "tableView": false }, { - "label": "Program name", - "disabled": true, - "tableView": true, - "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryProgramName", - "type": "textfield", + "type": "container", + "key": "pirSummary", "input": true, - "lockKey": true - }, - { - "label": "Offering name", - "disabled": true, - "tableView": true, + "tableView": false, + "label": "PIR Summary", + "hideLabel": true, "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryOfferingName", - "type": "textfield", - "input": true, - "lockKey": true + "components": [ + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "programName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "offeringName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] } ] }, diff --git a/sources/packages/forms/src/form-definitions/sfaa2024-25.json b/sources/packages/forms/src/form-definitions/sfaa2024-25.json index f322d57477..cf4fcac265 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2024-25.json +++ b/sources/packages/forms/src/form-definitions/sfaa2024-25.json @@ -895,7 +895,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!data.pirSummaryProgramName;", + "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", "type": "panel", "hideLabel": true, "tableView": false, @@ -914,26 +914,37 @@ "tableView": false }, { - "label": "Program name", - "disabled": true, - "tableView": true, - "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryProgramName", - "type": "textfield", + "type": "container", + "key": "pirSummary", "input": true, - "lockKey": true - }, - { - "label": "Offering name", - "disabled": true, - "tableView": true, + "tableView": false, + "label": "PIR Summary", + "hideLabel": true, "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryOfferingName", - "type": "textfield", - "input": true, - "lockKey": true + "components": [ + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "programName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "offeringName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] } ] }, diff --git a/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json b/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json index e11f30fb6f..1c591df1d2 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json +++ b/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json @@ -1208,7 +1208,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!data.pirSummaryProgramName;", + "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", "type": "panel", "hideLabel": true, "tableView": false, @@ -1227,26 +1227,37 @@ "tableView": false }, { - "label": "Program name", - "disabled": true, - "tableView": true, - "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryProgramName", - "type": "textfield", + "type": "container", + "key": "pirSummary", "input": true, - "lockKey": true - }, - { - "label": "Offering name", - "disabled": true, - "tableView": true, + "tableView": false, + "label": "PIR Summary", + "hideLabel": true, "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryOfferingName", - "type": "textfield", - "input": true, - "lockKey": true + "components": [ + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "programName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "offeringName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] } ] }, diff --git a/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json b/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json index c1cdc998d0..37af6abbd1 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json +++ b/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json @@ -1165,7 +1165,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!data.pirSummaryProgramName;", + "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", "type": "panel", "hideLabel": true, "tableView": false, @@ -1184,26 +1184,37 @@ "tableView": false }, { - "label": "Program name", - "disabled": true, - "tableView": true, - "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryProgramName", - "type": "textfield", + "type": "container", + "key": "pirSummary", "input": true, - "lockKey": true - }, - { - "label": "Offering name", - "disabled": true, - "tableView": true, + "tableView": false, + "label": "PIR Summary", + "hideLabel": true, "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryOfferingName", - "type": "textfield", - "input": true, - "lockKey": true + "components": [ + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "programName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "offeringName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] } ] }, diff --git a/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json b/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json index 121cf1772d..6d20749b4a 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json +++ b/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json @@ -1208,7 +1208,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!data.pirSummaryProgramName;", + "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", "type": "panel", "hideLabel": true, "tableView": false, @@ -1227,26 +1227,37 @@ "tableView": false }, { - "label": "Program name", - "disabled": true, - "tableView": true, - "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryProgramName", - "type": "textfield", + "type": "container", + "key": "pirSummary", "input": true, - "lockKey": true - }, - { - "label": "Offering name", - "disabled": true, - "tableView": true, + "tableView": false, + "label": "PIR Summary", + "hideLabel": true, "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryOfferingName", - "type": "textfield", - "input": true, - "lockKey": true + "components": [ + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "programName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "offeringName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] } ] }, diff --git a/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json b/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json index 2ba8d0e3b9..deb392cb4b 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json +++ b/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json @@ -1165,7 +1165,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!data.pirSummaryProgramName;", + "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", "type": "panel", "hideLabel": true, "tableView": false, @@ -1184,26 +1184,37 @@ "tableView": false }, { - "label": "Program name", - "disabled": true, - "tableView": true, - "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryProgramName", - "type": "textfield", + "type": "container", + "key": "pirSummary", "input": true, - "lockKey": true - }, - { - "label": "Offering name", - "disabled": true, - "tableView": true, + "tableView": false, + "label": "PIR Summary", + "hideLabel": true, "persistent": false, - "validateWhenHidden": false, - "key": "pirSummaryOfferingName", - "type": "textfield", - "input": true, - "lockKey": true + "components": [ + { + "label": "Program name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "programName", + "type": "textfield", + "input": true, + "lockKey": true + }, + { + "label": "Offering name", + "disabled": true, + "tableView": true, + "persistent": false, + "validateWhenHidden": false, + "key": "offeringName", + "type": "textfield", + "input": true, + "lockKey": true + } + ] } ] }, From c782ea88db6f2757ef93268898e67724b70491d3 Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Mon, 15 Jun 2026 11:28:10 -0700 Subject: [PATCH 5/9] Update application.students.controller.getApplication.e2e-spec.ts --- ...on.students.controller.getApplication.e2e-spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts index 30a266aa4e..12531121eb 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts @@ -205,11 +205,13 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { programName: "My Program", workflowName: "", programDescription: "This is my program.", - pirSummaryProgramName: - application.currentAssessment?.offering?.educationProgram.name, - pirSummaryOfferingName: getOfferingNameAndPeriod( - application.currentAssessment?.offering, - ), + pirSummary: { + programName: + application.currentAssessment!.offering!.educationProgram.name, + offeringName: getOfferingNameAndPeriod( + application.currentAssessment!.offering!, + ), + }, }, applicationStatus: application.applicationStatus, applicationEditStatus: application.applicationEditStatus, From 7654f9f66246ab8df37ce5315c8117756919b9b2 Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Mon, 15 Jun 2026 16:04:18 -0700 Subject: [PATCH 6/9] Updated based on PR Comments --- ...ntroller.getApplicationDetails.e2e-spec.ts | 15 +++---------- ...ntroller.getApplicationDetails.e2e-spec.ts | 13 +++-------- ...ents.controller.getApplication.e2e-spec.ts | 14 +++--------- .../application.aest.controller.ts | 3 +-- .../application.controller.service.ts | 11 +++++----- .../application.institutions.controller.ts | 1 - .../application.students.controller.ts | 1 - .../application/models/application.dto.ts | 4 ++-- .../backend/apps/api/src/testHelpers/index.ts | 1 + .../testHelpers/offering/offering-utils.ts | 22 +++++++++++++++++++ ...ts => education-program-offering-utils.ts} | 0 .../backend/apps/api/src/utilities/index.ts | 2 +- 12 files changed, 41 insertions(+), 46 deletions(-) create mode 100644 sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts rename sources/packages/backend/apps/api/src/utilities/{eductaion-program-offering-utils.ts => education-program-offering-utils.ts} (100%) diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts index a29f33b332..b864747d8a 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts @@ -5,6 +5,7 @@ import { BEARER_AUTH_TYPE, createTestingAppModule, getAESTToken, + getExpectedOfferingNameAndPeriod, } from "../../../../testHelpers"; import { createE2EDataSources, @@ -19,10 +20,7 @@ import { OfferingIntensity, ProgramInfoStatus, } from "@sims/sims-db"; -import { - getUserFullName, - getOfferingNameAndPeriod, -} from "../../../../utilities"; +import { getUserFullName } from "../../../../utilities"; import { addDays, getISODateOnlyString } from "@sims/utilities"; describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { @@ -261,12 +259,6 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { it("Should return PIR summary in application data when the application has PIR status completed and dynamic data is loaded.", async () => { // Arrange - const offeringInitialValues = { - name: "Test PIR Offering Name", - studyStartDate: "2024-09-01", - studyEndDate: "2025-04-30", - yearOfStudy: 2, - } as Partial; const application = await saveFakeApplication( db.dataSource, {}, @@ -274,7 +266,6 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { applicationStatus: ApplicationStatus.Assessment, offeringIntensity: OfferingIntensity.fullTime, pirStatus: ProgramInfoStatus.completed, - offeringInitialValues, }, ); @@ -292,7 +283,7 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { data: { pirSummary: { programName: savedOffering.educationProgram.name, - offeringName: getOfferingNameAndPeriod(savedOffering), + offeringName: getExpectedOfferingNameAndPeriod(savedOffering), }, }, }), diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts index ad0a8ae19a..f6f7434c14 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts @@ -5,6 +5,7 @@ import { BEARER_AUTH_TYPE, createTestingAppModule, getAuthRelatedEntities, + getExpectedOfferingNameAndPeriod, getInstitutionToken, INSTITUTION_BC_PUBLIC_ERROR_MESSAGE, INSTITUTION_STUDENT_DATA_ACCESS_ERROR_MESSAGE, @@ -27,7 +28,6 @@ import { import { addDays, getISODateOnlyString } from "@sims/utilities"; import { getUserFullName, - getOfferingNameAndPeriod, } from "../../../../utilities"; describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { @@ -281,12 +281,6 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { it("Should return PIR summary in application data when the application has PIR status completed.", async () => { // Arrange - const offeringInitialValues = { - name: "Test PIR Offering Name", - studyStartDate: "2024-09-01", - studyEndDate: "2025-04-30", - yearOfStudy: 2, - } as Partial; const savedApplication = await saveFakeApplication( db.dataSource, { institutionLocation: collegeFLocation }, @@ -294,7 +288,6 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { applicationStatus: ApplicationStatus.Assessment, offeringIntensity: OfferingIntensity.fullTime, pirStatus: ProgramInfoStatus.completed, - offeringInitialValues, }, ); const savedOffering = savedApplication.currentAssessment!.offering!; @@ -313,8 +306,8 @@ describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { expect(body).toMatchObject({ data: { pirSummary: { - programName: savedOffering!.educationProgram.name, - offeringName: getOfferingNameAndPeriod(savedOffering!), + programName: savedOffering.educationProgram.name, + offeringName: getExpectedOfferingNameAndPeriod(savedOffering), }, }, }), diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts index 12531121eb..cd93d42c6a 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.students.controller.getApplication.e2e-spec.ts @@ -4,6 +4,7 @@ import { BEARER_AUTH_TYPE, createTestingAppModule, FakeStudentUsersTypes, + getExpectedOfferingNameAndPeriod, getStudentToken, mockJWTUserInfo, resetMockJWTUserInfo, @@ -17,12 +18,10 @@ import { import { Application, ApplicationStatus, - EducationProgramOffering, OfferingIntensity, ProgramInfoStatus, Student, } from "@sims/sims-db"; -import { getOfferingNameAndPeriod } from "../../../../utilities"; import { getDateOnlyFormat } from "@sims/utilities"; import { TestingModule } from "@nestjs/testing"; @@ -208,7 +207,7 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { pirSummary: { programName: application.currentAssessment!.offering!.educationProgram.name, - offeringName: getOfferingNameAndPeriod( + offeringName: getExpectedOfferingNameAndPeriod( application.currentAssessment!.offering!, ), }, @@ -319,12 +318,6 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { it("Should return PIR summary in application data when the application has PIR status completed.", async () => { // Arrange - const offeringInitialValues = { - name: "Test PIR Offering Name", - studyStartDate: "2024-09-01", - studyEndDate: "2025-04-30", - yearOfStudy: 2, - } as Partial; const application = await saveFakeApplication( db.dataSource, { student }, @@ -332,7 +325,6 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { applicationStatus: ApplicationStatus.Assessment, offeringIntensity: OfferingIntensity.fullTime, pirStatus: ProgramInfoStatus.completed, - offeringInitialValues, }, ); const savedOffering = application.currentAssessment!.offering!; @@ -352,7 +344,7 @@ describe("ApplicationStudentsController(e2e)-getApplication", () => { data: { pirSummary: { programName: savedOffering.educationProgram.name, - offeringName: getOfferingNameAndPeriod(savedOffering), + offeringName: getExpectedOfferingNameAndPeriod(savedOffering), }, }, }), diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts index e05238d0a0..6939d22392 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.aest.controller.ts @@ -100,14 +100,13 @@ export class ApplicationAESTController extends BaseController { )); const currentReadOnlyDataPromise = this.applicationControllerService.generateApplicationFormData( - application.data, application, ); // If there is a previous application, generate its read-only data. const previousReadOnlyDataPromise = previousApplicationVersion && this.applicationControllerService.generateApplicationFormData( - previousApplicationVersion.data, + previousApplicationVersion, ); // Wait for both promises to resolve. [currentReadOnlyData, previousReadOnlyData] = await Promise.all([ diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts index 9bea0e2fc0..651f54ffee 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts @@ -119,15 +119,14 @@ export class ApplicationControllerService { * @returns ApplicationFormData. */ async generateApplicationFormData( - data: ApplicationData, - application?: Application, + application: Application, ): Promise { const additionalFormData = {} as ApplicationFormData; - await this.processSelectedLocation(data, additionalFormData); - await this.processSelectedProgram(data, additionalFormData); - await this.processSelectedOffering(data, additionalFormData); + await this.processSelectedLocation(application.data, additionalFormData); + await this.processSelectedProgram(application.data, additionalFormData); + await this.processSelectedOffering(application.data, additionalFormData); this.processPIRSummary(application, additionalFormData); - return { ...data, ...additionalFormData }; + return { ...application.data, ...additionalFormData }; } /** diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts index 926e4809e5..d0d7a0baa9 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.institutions.controller.ts @@ -75,7 +75,6 @@ export class ApplicationInstitutionsController extends BaseController { if (loadDynamicData) { application.data = await this.applicationControllerService.generateApplicationFormData( - application.data, application, ); } diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts index 0bd305441b..fdc1861039 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.students.controller.ts @@ -118,7 +118,6 @@ export class ApplicationStudentsController extends BaseController { const applicationDataPromise = this.applicationControllerService.generateApplicationFormData( - application.data, application, ); const firstCOEPromise = diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts b/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts index 8dea032a65..d498354459 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/models/application.dto.ts @@ -109,12 +109,12 @@ export interface ApplicationFormData extends ApplicationData { */ pirSummary?: { /** - * Program name from the completed PIR's current assessment offering. + * Program name from the completed PIR in the application's original assessment. */ programName?: string; /** * Formatted offering name (including year of study and dates) from the - * completed PIR's current assessment offering. + * completed PIR in the application's original assessment. */ offeringName?: string; }; diff --git a/sources/packages/backend/apps/api/src/testHelpers/index.ts b/sources/packages/backend/apps/api/src/testHelpers/index.ts index fd98af27f6..1a46651292 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/index.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/index.ts @@ -7,3 +7,4 @@ export * from "./testing-modules/testing-modules-helper"; export * from "./auth"; export * from "./fake-entities"; export * from "./program-year/program-year-helper"; +export * from "./offering/offering-utils"; diff --git a/sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts b/sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts new file mode 100644 index 0000000000..a90bae6f08 --- /dev/null +++ b/sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts @@ -0,0 +1,22 @@ +import { EducationProgramOffering } from "@sims/sims-db"; +import dayjs from "dayjs"; + +const DATE_ONLY_FORMAT = "MMM DD YYYY"; + +/** + * Builds the expected offering name string for test assertions, independent + * of the production utility {@link getOfferingNameAndPeriod}. + * Format: "Offering Name (MMM DD YYYY - MMM DD YYYY) - Year X" + * @param offering offering with name, studyStartDate, studyEndDate and yearOfStudy. + * @returns formatted offering name string. + */ +export function getExpectedOfferingNameAndPeriod( + offering: Pick< + EducationProgramOffering, + "name" | "studyStartDate" | "studyEndDate" | "yearOfStudy" + >, +): string { + const startDate = dayjs(offering.studyStartDate).format(DATE_ONLY_FORMAT); + const endDate = dayjs(offering.studyEndDate).format(DATE_ONLY_FORMAT); + return `${offering.name} (${startDate} - ${endDate}) - Year ${offering.yearOfStudy}`; +} diff --git a/sources/packages/backend/apps/api/src/utilities/eductaion-program-offering-utils.ts b/sources/packages/backend/apps/api/src/utilities/education-program-offering-utils.ts similarity index 100% rename from sources/packages/backend/apps/api/src/utilities/eductaion-program-offering-utils.ts rename to sources/packages/backend/apps/api/src/utilities/education-program-offering-utils.ts diff --git a/sources/packages/backend/apps/api/src/utilities/index.ts b/sources/packages/backend/apps/api/src/utilities/index.ts index b4dac31e42..da32d32c41 100644 --- a/sources/packages/backend/apps/api/src/utilities/index.ts +++ b/sources/packages/backend/apps/api/src/utilities/index.ts @@ -1,7 +1,7 @@ export * from "./system-configurations-constants"; export * from "./application-utils"; export * from "./auth-utils"; -export * from "./eductaion-program-offering-utils"; +export * from "./education-program-offering-utils"; export * from "./student-utils"; export * from "./credential-type-utils"; export * from "./upload-utils"; From 77ebe4e4be483704062a15001314a77bd386af3c Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Mon, 15 Jun 2026 16:06:34 -0700 Subject: [PATCH 7/9] Updated form definitions --- .../packages/forms/src/form-definitions/sfaa2022-23.json | 2 +- .../packages/forms/src/form-definitions/sfaa2023-24.json | 9 +++++++-- .../packages/forms/src/form-definitions/sfaa2024-25.json | 9 +++++++-- .../forms/src/form-definitions/sfaa2025-26-ft.json | 9 +++++++-- .../forms/src/form-definitions/sfaa2025-26-pt.json | 9 +++++++-- .../forms/src/form-definitions/sfaa2026-27-ft.json | 9 +++++++-- .../forms/src/form-definitions/sfaa2026-27-pt.json | 9 +++++++-- 7 files changed, 43 insertions(+), 13 deletions(-) diff --git a/sources/packages/forms/src/form-definitions/sfaa2022-23.json b/sources/packages/forms/src/form-definitions/sfaa2022-23.json index a1738aeda2..4d755ff224 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2022-23.json +++ b/sources/packages/forms/src/form-definitions/sfaa2022-23.json @@ -889,7 +889,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", + "customConditional": "show = !!data.pirSummary;", "type": "panel", "hideLabel": true, "tableView": false, diff --git a/sources/packages/forms/src/form-definitions/sfaa2023-24.json b/sources/packages/forms/src/form-definitions/sfaa2023-24.json index 4a57fffd54..8cd9ddf826 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2023-24.json +++ b/sources/packages/forms/src/form-definitions/sfaa2023-24.json @@ -889,7 +889,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", + "customConditional": "show = !!data.pirSummary;", "type": "panel", "hideLabel": true, "tableView": false, @@ -899,7 +899,12 @@ "label": "HTML", "tag": "h4", "className": "category-header-medium-small", - "attrs": [{ "attr": "", "value": "" }], + "attrs": [ + { + "attr": "", + "value": "" + } + ], "content": "Program information request summary", "refreshOnChange": false, "key": "pirSummaryHeader", diff --git a/sources/packages/forms/src/form-definitions/sfaa2024-25.json b/sources/packages/forms/src/form-definitions/sfaa2024-25.json index cf4fcac265..706a4ae08a 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2024-25.json +++ b/sources/packages/forms/src/form-definitions/sfaa2024-25.json @@ -895,7 +895,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", + "customConditional": "show = !!data.pirSummary;", "type": "panel", "hideLabel": true, "tableView": false, @@ -905,7 +905,12 @@ "label": "HTML", "tag": "h4", "className": "category-header-medium-small", - "attrs": [{ "attr": "", "value": "" }], + "attrs": [ + { + "attr": "", + "value": "" + } + ], "content": "Program information request summary", "refreshOnChange": false, "key": "pirSummaryHeader", diff --git a/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json b/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json index 1c591df1d2..11d48ce876 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json +++ b/sources/packages/forms/src/form-definitions/sfaa2025-26-ft.json @@ -1208,7 +1208,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", + "customConditional": "show = !!data.pirSummary;", "type": "panel", "hideLabel": true, "tableView": false, @@ -1218,7 +1218,12 @@ "label": "HTML", "tag": "h4", "className": "category-header-medium-small", - "attrs": [{ "attr": "", "value": "" }], + "attrs": [ + { + "attr": "", + "value": "" + } + ], "content": "Program information request summary", "refreshOnChange": false, "key": "pirSummaryHeader", diff --git a/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json b/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json index 37af6abbd1..ef4cfbe2da 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json +++ b/sources/packages/forms/src/form-definitions/sfaa2025-26-pt.json @@ -1165,7 +1165,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", + "customConditional": "show = !!data.pirSummary;", "type": "panel", "hideLabel": true, "tableView": false, @@ -1175,7 +1175,12 @@ "label": "HTML", "tag": "h4", "className": "category-header-medium-small", - "attrs": [{ "attr": "", "value": "" }], + "attrs": [ + { + "attr": "", + "value": "" + } + ], "content": "Program information request summary", "refreshOnChange": false, "key": "pirSummaryHeader", diff --git a/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json b/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json index 6d20749b4a..d6aff31ea7 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json +++ b/sources/packages/forms/src/form-definitions/sfaa2026-27-ft.json @@ -1208,7 +1208,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", + "customConditional": "show = !!data.pirSummary;", "type": "panel", "hideLabel": true, "tableView": false, @@ -1218,7 +1218,12 @@ "label": "HTML", "tag": "h4", "className": "category-header-medium-small", - "attrs": [{ "attr": "", "value": "" }], + "attrs": [ + { + "attr": "", + "value": "" + } + ], "content": "Program information request summary", "refreshOnChange": false, "key": "pirSummaryHeader", diff --git a/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json b/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json index deb392cb4b..b8a6a9a5a7 100644 --- a/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json +++ b/sources/packages/forms/src/form-definitions/sfaa2026-27-pt.json @@ -1165,7 +1165,7 @@ "key": "pirSummaryPanel", "label": "Program information request summary", "input": false, - "customConditional": "show = !!(data.pirSummary && data.pirSummary.programName);", + "customConditional": "show = !!data.pirSummary;", "type": "panel", "hideLabel": true, "tableView": false, @@ -1175,7 +1175,12 @@ "label": "HTML", "tag": "h4", "className": "category-header-medium-small", - "attrs": [{ "attr": "", "value": "" }], + "attrs": [ + { + "attr": "", + "value": "" + } + ], "content": "Program information request summary", "refreshOnChange": false, "key": "pirSummaryHeader", From 64aa9f7f2692ff630f3eadd9eac829fd3b467439 Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Mon, 15 Jun 2026 16:59:39 -0700 Subject: [PATCH 8/9] Load PIR info from original assessment --- ...ntroller.getApplicationDetails.e2e-spec.ts | 60 +++++++++++++++++++ ...ntroller.getApplicationDetails.e2e-spec.ts | 4 +- .../application.controller.service.ts | 16 +++-- .../application/application.service.ts | 24 +++++++- .../testHelpers/offering/offering-utils.ts | 8 +-- 5 files changed, 98 insertions(+), 14 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts index b864747d8a..c9ebba7e1a 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.aest.controller.getApplicationDetails.e2e-spec.ts @@ -9,6 +9,8 @@ import { } from "../../../../testHelpers"; import { createE2EDataSources, + createFakeEducationProgramOffering, + createFakeStudentAssessment, E2EDataSources, saveFakeApplication, } from "@sims/test-utils"; @@ -16,6 +18,7 @@ import { Application, ApplicationData, ApplicationStatus, + AssessmentTriggerType, EducationProgramOffering, OfferingIntensity, ProgramInfoStatus, @@ -312,6 +315,63 @@ describe("ApplicationAESTController(e2e)-getApplicationDetails", () => { .expect(({ body }) => expect(body.data.pirSummary).toBeUndefined()); }); + it("Should return the PIR summary from the original assessment offering even when the application has a later reassessment with a different offering.", async () => { + // Arrange + // Create an application with PIR completed (original assessment). + const application = await saveFakeApplication( + db.dataSource, + {}, + { + applicationStatus: ApplicationStatus.Assessment, + offeringIntensity: OfferingIntensity.fullTime, + pirStatus: ProgramInfoStatus.completed, + }, + ); + const originalOffering = application.currentAssessment!.offering!; + // Create a different offering for the reassessment to confirm that the + // PIR summary always reflects the original assessment's offering. + // PIR is assessed once per application (tied to the original assessment), + // so a subsequent reassessment with a new offering must not affect the PIR summary. + const differentOffering = createFakeEducationProgramOffering({ + auditUser: application.student.user, + }); + differentOffering.parentOffering = differentOffering; + const savedDifferentOffering = + await db.educationProgramOffering.save(differentOffering); + const reassessment = createFakeStudentAssessment( + { + auditUser: application.student.user, + application, + offering: savedDifferentOffering, + }, + { + initialValue: { + triggerType: AssessmentTriggerType.StudentAppeal, + }, + }, + ); + application.currentAssessment = reassessment; + await db.application.save(application); + const token = await getAESTToken(AESTGroups.BusinessAdministrators); + const endpoint = `/aest/application/${application.id}`; + + // Act/Assert + await request(app.getHttpServer()) + .get(endpoint) + .auth(token, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK) + .expect(({ body }) => + expect(body).toMatchObject({ + data: { + pirSummary: { + programName: originalOffering.educationProgram.name, + offeringName: getExpectedOfferingNameAndPeriod(originalOffering), + }, + }, + }), + ); + }); + afterAll(async () => { await app?.close(); }); diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts index f6f7434c14..2e93b0e148 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/_tests_/e2e/application.institutions.controller.getApplicationDetails.e2e-spec.ts @@ -26,9 +26,7 @@ import { ProgramInfoStatus, } from "@sims/sims-db"; import { addDays, getISODateOnlyString } from "@sims/utilities"; -import { - getUserFullName, -} from "../../../../utilities"; +import { getUserFullName } from "../../../../utilities"; describe("ApplicationInstitutionsController(e2e)-getApplicationDetails", () => { let app: INestApplication; diff --git a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts index 651f54ffee..b40894219d 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application/application.controller.service.ts @@ -71,6 +71,7 @@ import { DynamicFormType, ProgramInfoStatus, StudentScholasticStandingChangeType, + AssessmentTriggerType, } from "@sims/sims-db"; import { ApiProcessError } from "../../types"; import { ACTIVE_STUDENT_RESTRICTION } from "../../constants"; @@ -131,9 +132,10 @@ export class ApplicationControllerService { /** * Enriches the application form data with the PIR outcome summary when the PIR - * has been completed. The summary data is sourced from the current assessment - * offering which holds the PIR-approved program and offering information. - * @param application application entity loaded with current assessment offering + * has been completed. The summary data is sourced from the original assessment + * offering, since PIR is assessed once per application and is tied to the + * original submission, regardless of any subsequent reassessments. + * @param application application entity loaded with the original assessment offering * and education program data. * @param formData form data object to be enriched with the PIR summary. */ @@ -144,10 +146,14 @@ export class ApplicationControllerService { if (application?.pirStatus !== ProgramInfoStatus.completed) { return; } - const offering = application.currentAssessment?.offering; - if (!offering) { + const originalAssessment = application.studentAssessments?.find( + (assessment) => + assessment.triggerType === AssessmentTriggerType.OriginalAssessment, + ); + if (!originalAssessment?.offering) { return; } + const offering = originalAssessment.offering; formData.pirSummary = { programName: offering.educationProgram.name, offeringName: getOfferingNameAndPeriod(offering), diff --git a/sources/packages/backend/apps/api/src/services/application/application.service.ts b/sources/packages/backend/apps/api/src/services/application/application.service.ts index 54d665b47c..d9dd4f7e5a 100644 --- a/sources/packages/backend/apps/api/src/services/application/application.service.ts +++ b/sources/packages/backend/apps/api/src/services/application/application.service.ts @@ -937,7 +937,7 @@ export class ApplicationService extends RecordDataModelService { * @param options object that should contain: * - `loadDynamicData` indicates if the dynamic data(JSONB) should be loaded. When true, * also loads the additional offering details (name, year of study, and education program) - * needed to display the PIR outcome summary. + * from the original assessment needed to display the PIR outcome summary. * - `studentId` student id. * - `entityManager` entity manager to be used for the query. Useful when * it needs to be executed in a transaction. @@ -1025,6 +1025,25 @@ export class ApplicationService extends RecordDataModelService { lastName: true, }, }, + ...(options?.loadDynamicData + ? { + studentAssessments: { + id: true, + triggerType: true, + offering: { + id: true, + name: true, + yearOfStudy: true, + studyStartDate: true, + studyEndDate: true, + educationProgram: { + id: true, + name: true, + }, + }, + }, + } + : {}), }, relations: { applicationException: true, @@ -1040,6 +1059,9 @@ export class ApplicationService extends RecordDataModelService { precedingApplication: { currentAssessment: { offering: true, studentAppeal: true }, }, + ...(options?.loadDynamicData + ? { studentAssessments: { offering: { educationProgram: true } } } + : {}), }, where: { id: applicationId, diff --git a/sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts b/sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts index a90bae6f08..713e0f500a 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/offering/offering-utils.ts @@ -1,7 +1,5 @@ import { EducationProgramOffering } from "@sims/sims-db"; -import dayjs from "dayjs"; - -const DATE_ONLY_FORMAT = "MMM DD YYYY"; +import { getDateOnlyFormat } from "@sims/utilities/date-utils"; /** * Builds the expected offering name string for test assertions, independent @@ -16,7 +14,7 @@ export function getExpectedOfferingNameAndPeriod( "name" | "studyStartDate" | "studyEndDate" | "yearOfStudy" >, ): string { - const startDate = dayjs(offering.studyStartDate).format(DATE_ONLY_FORMAT); - const endDate = dayjs(offering.studyEndDate).format(DATE_ONLY_FORMAT); + const startDate = getDateOnlyFormat(offering.studyStartDate); + const endDate = getDateOnlyFormat(offering.studyEndDate); return `${offering.name} (${startDate} - ${endDate}) - Year ${offering.yearOfStudy}`; } From c2c19744123d2e87c8c88d1b635943dc16cb52af Mon Sep 17 00:00:00 2001 From: Tiago Graf Date: Mon, 15 Jun 2026 17:39:08 -0700 Subject: [PATCH 9/9] Update application.service.ts --- .../services/application/application.service.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/sources/packages/backend/apps/api/src/services/application/application.service.ts b/sources/packages/backend/apps/api/src/services/application/application.service.ts index d9dd4f7e5a..9d29e21717 100644 --- a/sources/packages/backend/apps/api/src/services/application/application.service.ts +++ b/sources/packages/backend/apps/api/src/services/application/application.service.ts @@ -989,14 +989,6 @@ export class ApplicationService extends RecordDataModelService { studyStartDate: true, studyEndDate: true, offeringStatus: true, - ...(options?.loadDynamicData && { - name: true, - yearOfStudy: true, - educationProgram: { - id: true, - name: true, - }, - }), }, }, location: { @@ -1047,11 +1039,7 @@ export class ApplicationService extends RecordDataModelService { }, relations: { applicationException: true, - currentAssessment: { - offering: options?.loadDynamicData - ? { educationProgram: true } - : true, - }, + currentAssessment: { offering: true }, location: { institution: true }, pirDeniedReasonId: true, programYear: true,