Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bed139e
#5397 - Non Punitive Withdrawal Form
sh16011993 Mar 20, 2026
d568334
Merge branch 'main' into feature/#5397-non-punitive-withdrawal-form
sh16011993 Mar 20, 2026
b8bb94a
Code Update
sh16011993 Mar 20, 2026
9bc9617
Code Update
sh16011993 Mar 26, 2026
810d8de
Code merged from main
sh16011993 Mar 26, 2026
120004b
Revert "Code merged from main"
sh16011993 Mar 26, 2026
e3381bb
Merge branch 'main' into feature/#5397-non-punitive-withdrawal-form
sh16011993 Mar 26, 2026
f7ec2c4
Code Update
sh16011993 Mar 26, 2026
550b42f
Code Update
sh16011993 Mar 26, 2026
bf0878b
Code Update
sh16011993 Mar 26, 2026
bbd1e83
Code Update
sh16011993 Mar 26, 2026
deb9cc2
Update sources/packages/backend/apps/api/src/services/form-submission…
sh16011993 Mar 27, 2026
3dec207
Update sources/packages/backend/apps/api/src/services/form-submission…
sh16011993 Mar 27, 2026
cf6e22f
Merge branch 'main' into feature/#5397-non-punitive-withdrawal-form
sh16011993 Mar 27, 2026
b9ca3d5
Change nonPunitiveFormSubmissionItemId from Column to OneToOne relati…
Copilot Mar 27, 2026
61cfdc4
Revert "Change nonPunitiveFormSubmissionItemId from Column to OneToOn…
Copilot Mar 27, 2026
934e6d4
Code Update
sh16011993 Mar 27, 2026
9b31da9
Reapply "Change nonPunitiveFormSubmissionItemId from Column to OneToO…
Copilot Mar 27, 2026
f4fa8e0
Merge remote-tracking branch 'origin/feature/#5397-non-punitive-withd…
sh16011993 Mar 27, 2026
6c1ead1
Code Update
sh16011993 Mar 27, 2026
f2db001
Code Update
sh16011993 Mar 27, 2026
8d4d37f
- Fixed Module Import
sh16011993 Mar 27, 2026
1ce4151
Code Update
sh16011993 Mar 27, 2026
7c82dc7
Added e2e test for archived application
sh16011993 Mar 27, 2026
dc963bd
Update sources/packages/forms/src/form-definitions/nonpunitivewithdra…
sh16011993 Mar 27, 2026
8c9f429
Update sources/packages/web/src/assets/css/base.scss
sh16011993 Mar 27, 2026
041de63
Fixed array name
sh16011993 Mar 27, 2026
4cda164
Code Update
sh16011993 Mar 27, 2026
c1f8979
Merge branch 'main' into feature/#5397-non-punitive-withdrawal-form
sh16011993 Mar 27, 2026
dcbb724
Code Update
sh16011993 Mar 31, 2026
bdc4af4
Code Update
sh16011993 Mar 31, 2026
1142097
Code Update
sh16011993 Apr 1, 2026
7c5eced
Code Update
sh16011993 Apr 1, 2026
e013317
Code Update
sh16011993 Apr 1, 2026
d568633
Code Update
sh16011993 Apr 1, 2026
291a97f
Cod eUpdate
sh16011993 Apr 1, 2026
2ec46ea
Code Update
sh16011993 Apr 1, 2026
d2ba77d
Code Update
sh16011993 Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sources/packages/backend/apps/api/src/app.aest.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
FormSubmissionActionProcessor,
FormSubmissionCreateAppealAssessmentAction,
FormSubmissionUpdateModifiedIndependentAction,
FormSubmissionUpdateNonPunitiveWithdrawalAction,
} from "./services";
import {
ConfigurationContextValidator,
Expand All @@ -59,6 +60,7 @@ import {
SupplementaryDataProgramYear,
SupplementaryDataParents,
SupplementaryDataLoader,
SupplementaryDataScholasticStandingWithdrawals,
} from "./services/form-submission/form-supplementary-data";
import {
SupportingUserAESTController,
Expand Down Expand Up @@ -258,11 +260,13 @@ import { ECertIntegrationModule } from "@sims/integrations/esdc-integration";
// Form supplementary data loaders.
SupplementaryDataProgramYear,
SupplementaryDataParents,
SupplementaryDataScholasticStandingWithdrawals,
SupplementaryDataLoader,
FormSubmissionService,
// Form submission actions.
FormSubmissionCreateAppealAssessmentAction,
FormSubmissionUpdateModifiedIndependentAction,
FormSubmissionUpdateNonPunitiveWithdrawalAction,
FormSubmissionActionProcessor,
FormSubmissionApprovalService,
],
Expand Down
2 changes: 2 additions & 0 deletions sources/packages/backend/apps/api/src/app.students.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import {
SupplementaryDataProgramYear,
SupplementaryDataParents,
SupplementaryDataLoader,
SupplementaryDataScholasticStandingWithdrawals,
} from "./services/form-submission/form-supplementary-data";

@Module({
Expand Down Expand Up @@ -183,6 +184,7 @@ import {
// Form Supplementary Data Loaders
SupplementaryDataProgramYear,
SupplementaryDataParents,
SupplementaryDataScholasticStandingWithdrawals,
SupplementaryDataLoader,
// Form Submission Service.
FormSubmissionSubmitService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,9 @@ export class AssessmentControllerService {
assessment.studentScholasticStanding?.changeType,
scholasticStandingReversalDate:
assessment.studentScholasticStanding?.reversalDate,
nonPunitiveFormSubmissionItemId:
assessment.studentScholasticStanding?.nonPunitiveFormSubmissionItem
?.id,
}),
);
const unsuccessfulScholasticStandingHistory =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export class AssessmentHistorySummaryAPIOutDTO {
* Date and time when the scholastic standing was reversed.
*/
scholasticStandingReversalDate?: Date;
/**
* Id of the non-punitive form submission item.
*/
nonPunitiveFormSubmissionItemId?: number;
/**
* This flag decides, the row is unsuccessful week or not.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { HttpStatus, INestApplication } from "@nestjs/common";
import * as request from "supertest";
import {
AESTGroups,
BEARER_AUTH_TYPE,
createTestingAppModule,
FakeStudentUsersTypes,
getAESTUser,
getStudentByFakeStudentUserType,
getStudentToken,
mockJWTUserInfo,
Expand All @@ -12,24 +14,47 @@ import {
import { KnownSupplementaryDataKey } from "../../../../services";
import {
createE2EDataSources,
createFakeInstitutionUser,
createFakeSupportingUser,
createFakeStudentScholasticStanding,
E2EDataSources,
saveFakeApplication,
saveFakeFormSubmissionFromInputTestData,
ensureDynamicFormConfigurationExists,
} from "@sims/test-utils";
import { TestingModule } from "@nestjs/testing";
import { SupportingUserType } from "@sims/sims-db";
import {
ApplicationStatus,
DynamicFormType,
FormCategory,
FormSubmissionDecisionStatus,
FormSubmissionStatus,
StudentScholasticStandingChangeType,
SupportingUserType,
User,
} from "@sims/sims-db";

describe("FormSubmissionStudentsController(e2e)-getSupplementaryData", () => {
let app: INestApplication;
let appModule: TestingModule;
let db: E2EDataSources;
let auditUser: User;
let institutionSubmittedByUser: User;

beforeAll(async () => {
const { nestApplication, dataSource, module } =
await createTestingAppModule();
app = nestApplication;
appModule = module;
db = createE2EDataSources(dataSource);
auditUser = await getAESTUser(
dataSource,
AESTGroups.BusinessAdministrators,
);
const savedInstitutionUser = await db.institutionUser.save(
createFakeInstitutionUser(),
);
institutionSubmittedByUser = savedInstitutionUser.user;
});

beforeEach(async () => {
Expand Down Expand Up @@ -81,7 +106,7 @@ describe("FormSubmissionStudentsController(e2e)-getSupplementaryData", () => {
});
});

it(`Should throw an unprocessable entity exception when requesting supplementary data for ${KnownSupplementaryDataKey.ProgramYear} and an application ID was not provided.`, async () => {
it(`Should throw an unprocessable entity exception when requesting supplementary data for ${KnownSupplementaryDataKey.ProgramYear} and an application ID was not provided.`, async () => {
// Arrange
const endpoint = `/students/form-submission/supplementary-data?dataKeys=${KnownSupplementaryDataKey.ProgramYear}`;
const studentToken = await getStudentToken(
Expand Down Expand Up @@ -109,7 +134,12 @@ describe("FormSubmissionStudentsController(e2e)-getSupplementaryData", () => {
const [parent1, parent2] = Array.from({ length: 2 }, (_, index) =>
createFakeSupportingUser(
{ application },
{ initialValues: { supportingUserType: SupportingUserType.Parent, fullName: `Parent ${index + 1}` } },
{
initialValues: {
supportingUserType: SupportingUserType.Parent,
fullName: `Parent ${index + 1}`,
},
},
),
);
await db.supportingUser.save([parent1, parent2]);
Expand Down Expand Up @@ -178,14 +208,215 @@ describe("FormSubmissionStudentsController(e2e)-getSupplementaryData", () => {
});
});

describe(`Supplementary data validations for ${KnownSupplementaryDataKey.ScholasticStandingWithdrawals}.`, () => {
it(`Should get supplementary data for ${KnownSupplementaryDataKey.ScholasticStandingWithdrawals} when there is a scholastic standing of change type ${StudentScholasticStandingChangeType.StudentWithdrewFromProgram} that is not reversed and a non-punitive withdrawal form has not been previously submitted for this application.`, async () => {
// Arrange
const application = await saveFakeApplication(db.dataSource, undefined, {
initialValues: {
applicationStatus: ApplicationStatus.Completed,
},
});
const scholasticStanding = createFakeStudentScholasticStanding(
{ submittedBy: institutionSubmittedByUser, application },
{
initialValues: {
changeType:
StudentScholasticStandingChangeType.StudentWithdrewFromProgram,
},
},
);
const savedScholasticStanding =
await db.studentScholasticStanding.save(scholasticStanding);
// Link the assessment to the scholastic standing.
application.currentAssessment.studentScholasticStanding =
savedScholasticStanding;
await db.studentAssessment.save(application.currentAssessment);
const endpoint = `/students/form-submission/supplementary-data?dataKeys=${KnownSupplementaryDataKey.ScholasticStandingWithdrawals}`;
const studentToken = await getStudentToken(
FakeStudentUsersTypes.FakeStudentUserType1,
);
// Mock the user received in the token.
await mockJWTUserInfo(appModule, application.student.user);

// Act/Assert
const response = await request(app.getHttpServer())
.get(endpoint)
.auth(studentToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.OK);
expect(response.body).toStrictEqual({
formData: {
scholasticStandingWithdrawals: [
{
applicationNumber: application.applicationNumber,
scholasticStandingId: savedScholasticStanding.id,
},
],
},
});
});

it(
`Should not return supplementary data for ${KnownSupplementaryDataKey.ScholasticStandingWithdrawals} when there is a scholastic standing of change type ${StudentScholasticStandingChangeType.StudentWithdrewFromProgram} ` +
"that is not reversed and a non-punitive withdrawal form has not been previously submitted for this application but the application has been archived.",
async () => {
// Arrange
const application = await saveFakeApplication(
db.dataSource,
undefined,
{
isArchived: true,
},
);
const scholasticStanding = createFakeStudentScholasticStanding(
{ submittedBy: institutionSubmittedByUser, application },
{
initialValues: {
changeType:
StudentScholasticStandingChangeType.StudentWithdrewFromProgram,
},
},
);
const savedScholasticStanding =
await db.studentScholasticStanding.save(scholasticStanding);
// Link the assessment to the scholastic standing.
application.currentAssessment.studentScholasticStanding =
savedScholasticStanding;
await db.studentAssessment.save(application.currentAssessment);
const endpoint = `/students/form-submission/supplementary-data?dataKeys=${KnownSupplementaryDataKey.ScholasticStandingWithdrawals}`;
const studentToken = await getStudentToken(
FakeStudentUsersTypes.FakeStudentUserType1,
);
// Mock the user received in the token.
await mockJWTUserInfo(appModule, application.student.user);

// Act/Assert
const response = await request(app.getHttpServer())
.get(endpoint)
.auth(studentToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.OK);
expect(response.body).toStrictEqual({
formData: {
scholasticStandingWithdrawals: [],
},
});
},
);

it(`Should not return supplementary data for ${KnownSupplementaryDataKey.ScholasticStandingWithdrawals} when the ${StudentScholasticStandingChangeType.StudentWithdrewFromProgram} scholastic standing is reversed.`, async () => {
// Arrange
const application = await saveFakeApplication(db.dataSource);
const scholasticStanding = createFakeStudentScholasticStanding(
{ submittedBy: institutionSubmittedByUser, application },
{
initialValues: {
changeType:
StudentScholasticStandingChangeType.StudentWithdrewFromProgram,
reversalDate: new Date(),
},
},
);
const savedScholasticStanding =
await db.studentScholasticStanding.save(scholasticStanding);
// Link the assessment to the scholastic standing.
application.currentAssessment.studentScholasticStanding =
savedScholasticStanding;
await db.studentAssessment.save(application.currentAssessment);
const endpoint = `/students/form-submission/supplementary-data?dataKeys=${KnownSupplementaryDataKey.ScholasticStandingWithdrawals}`;
const studentToken = await getStudentToken(
FakeStudentUsersTypes.FakeStudentUserType1,
);
// Mock the user received in the token.
await mockJWTUserInfo(appModule, application.student.user);

// Act/Assert
const response = await request(app.getHttpServer())
.get(endpoint)
.auth(studentToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.OK);
expect(response.body).toStrictEqual({
formData: { scholasticStandingWithdrawals: [] },
});
});

it(
`Should not return supplementary data for ${KnownSupplementaryDataKey.ScholasticStandingWithdrawals} when the ${StudentScholasticStandingChangeType.StudentWithdrewFromProgram} scholastic standing ` +
"has a non-punitive withdrawal form that is previously submitted for this application.",
async () => {
// Arrange
const application = await saveFakeApplication(db.dataSource);
// Create a fake FormSubmission to simulate a previously submitted non-punitive withdrawal form.
const dynamicFormConfiguration =
await ensureDynamicFormConfigurationExists(
db,
DynamicFormType.NonPunitiveWithdrawal,
);
const savedFormSubmission =
await saveFakeFormSubmissionFromInputTestData(db, {
student: application.student,
formCategory: FormCategory.StudentForm,
submissionStatus: FormSubmissionStatus.Completed,
ministryAuditUser: auditUser,
formSubmissionItems: [
{
dynamicFormConfiguration,
decisions: [
{
decisionStatus: FormSubmissionDecisionStatus.Approved,
},
],
},
],
});
const [savedFormSubmissionItem] =
savedFormSubmission.formSubmissionItems;
const scholasticStanding = createFakeStudentScholasticStanding(
{ submittedBy: institutionSubmittedByUser, application },
{
initialValues: {
changeType:
StudentScholasticStandingChangeType.StudentWithdrewFromProgram,
nonPunitiveFormSubmissionItem: savedFormSubmissionItem,
},
},
);
const savedScholasticStanding =
await db.studentScholasticStanding.save(scholasticStanding);
// Link the assessment to the scholastic standing.
application.currentAssessment.studentScholasticStanding =
savedScholasticStanding;
await db.studentAssessment.save(application.currentAssessment);
const endpoint = `/students/form-submission/supplementary-data?dataKeys=${KnownSupplementaryDataKey.ScholasticStandingWithdrawals}`;
const studentToken = await getStudentToken(
FakeStudentUsersTypes.FakeStudentUserType1,
);
// Mock the user received in the token.
await mockJWTUserInfo(appModule, application.student.user);

// Act/Assert
const response = await request(app.getHttpServer())
.get(endpoint)
.auth(studentToken, BEARER_AUTH_TYPE)
.expect(HttpStatus.OK);
expect(response.body).toStrictEqual({
formData: { scholasticStandingWithdrawals: [] },
});
},
);
});

describe(`Supplementary data validations common for all data keys.`, () => {
it("Should get supplementary data for two different data keys when two data keys are provided and the data is available.", async () => {
// Arrange
const application = await saveFakeApplication(db.dataSource);
// Create supporting users.
const parent = createFakeSupportingUser(
{ application },
{ initialValues: { supportingUserType: SupportingUserType.Parent, fullName: "Parent 1" } },
{
initialValues: {
supportingUserType: SupportingUserType.Parent,
fullName: "Parent 1",
},
},
);
await db.supportingUser.save([parent]);

Expand Down Expand Up @@ -223,7 +454,7 @@ describe("FormSubmissionStudentsController(e2e)-getSupplementaryData", () => {
.expect(HttpStatus.BAD_REQUEST)
.expect({
message: [
"each value in dataKeys must be one of the following values: programYear, parents",
"each value in dataKeys must be one of the following values: programYear, parents, scholasticStandingWithdrawals",
"dataKeys must contain no more than 10 elements",
"dataKeys must contain at least 1 elements",
],
Expand All @@ -246,7 +477,7 @@ describe("FormSubmissionStudentsController(e2e)-getSupplementaryData", () => {
.expect(HttpStatus.BAD_REQUEST)
.expect({
message: [
"each value in dataKeys must be one of the following values: programYear, parents",
"each value in dataKeys must be one of the following values: programYear, parents, scholasticStandingWithdrawals",
],
error: "Bad Request",
statusCode: HttpStatus.BAD_REQUEST,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@ describe("StudentScholasticStandingsStudentsController(e2e)-getScholasticStandin
},
},
);
const savedScholasticStanding = await db.studentScholasticStanding.save(
scholasticStanding,
);
const savedScholasticStanding =
await db.studentScholasticStanding.save(scholasticStanding);
const endpoint = `/students/scholastic-standing/${savedScholasticStanding.id}`;
const token = await getStudentToken(
FakeStudentUsersTypes.FakeStudentUserType1,
Expand Down
Loading
Loading