Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 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 @@ -46,6 +46,7 @@ import {
FormSubmissionActionProcessor,
FormSubmissionCreateAppealAssessmentAction,
FormSubmissionUpdateModifiedIndependentAction,
FormSubmissionService,
} from "./services";
import {
SupportingUserAESTController,
Expand Down Expand Up @@ -237,6 +238,7 @@ import { ECertIntegrationModule } from "@sims/integrations/esdc-integration";
FormSubmissionUpdateModifiedIndependentAction,
FormSubmissionActionProcessor,
FormSubmissionApprovalService,
FormSubmissionService,
],
})
export class AppAESTModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
SupportingUserService,
ApplicationRestrictionBypassService,
InstitutionRestrictionService,
FormSubmissionService,
} from "./services";
import {
ApplicationControllerService,
Expand Down Expand Up @@ -78,6 +79,8 @@ import {
ProgramYearControllerService,
ReportControllerService,
AnnouncementInstitutionsController,
FormSubmissionInstitutionsController,
FormSubmissionControllerService,
} from "./route-controllers";
import { AuthModule } from "./auth/auth.module";
import {
Expand Down Expand Up @@ -126,6 +129,7 @@ import { ECertIntegrationModule } from "@sims/integrations/esdc-integration";
ApplicationOfferingChangeRequestInstitutionsController,
ProgramYearInstitutionsController,
ReportInstitutionsController,
FormSubmissionInstitutionsController,
],
providers: [
AnnouncementService,
Expand Down Expand Up @@ -196,6 +200,8 @@ import { ECertIntegrationModule } from "@sims/integrations/esdc-integration";
SupportingUserService,
DisbursementScheduleSharedService,
InstitutionRestrictionService,
FormSubmissionService,
FormSubmissionControllerService,
],
})
export class AppInstitutionsModule {}
4 changes: 4 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 @@ -29,6 +29,7 @@ import {
AnnouncementService,
ApplicationRestrictionBypassService,
InstitutionRestrictionService,
FormSubmissionSubmitService,
FormSubmissionService,
} from "./services";
import {
Expand Down Expand Up @@ -58,6 +59,7 @@ import {
SupportingUserControllerService,
SupportingUserStudentsController,
FormSubmissionStudentsController,
FormSubmissionControllerService,
} from "./route-controllers";
import { AuthModule } from "./auth/auth.module";
import { ConfigModule } from "@sims/utilities/config";
Expand Down Expand Up @@ -183,6 +185,8 @@ import {
SupplementaryDataParents,
SupplementaryDataLoader,
// Form Submission Service.
FormSubmissionSubmitService,
FormSubmissionControllerService,
FormSubmissionService,
],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
MASKED_MSFAA_NUMBER,
ApplicationOfferingChangeRequestService,
MASKED_MONEY_AMOUNT,
FormSubmissionService,
} from "../../services";
import {
AssessmentNOAAPIOutDTO,
Expand Down Expand Up @@ -55,6 +56,7 @@ export class AssessmentControllerService {
constructor(
private readonly assessmentService: StudentAssessmentService,
private readonly studentAppealService: StudentAppealService,
private readonly formSubmissionService: FormSubmissionService,
private readonly studentScholasticStandingsService: StudentScholasticStandingsService,
private readonly educationProgramOfferingService: EducationProgramOfferingService,
private readonly applicationExceptionService: ApplicationExceptionService,
Expand Down Expand Up @@ -488,6 +490,31 @@ export class AssessmentControllerService {
return studentAppealArray;
}

/**
* Get student appeals that not generated an assessment yet, which are usually pending or denied appeals.
* @param applicationId application to which the requests are retrieved.
* @param studentId applicant student.
* @returns pending and denied student appeals.
*/
async getPendingAndDeniedStudentAppeals(
applicationId: number,
studentId?: number,
): Promise<RequestAssessmentSummaryAPIOutDTO[]> {
const formSubmissions =
await this.formSubmissionService.getNonCompletedStudentAppeals(
applicationId,
studentId,
);
const submissions: RequestAssessmentSummaryAPIOutDTO[] =
formSubmissions.map((submission) => ({
id: submission.id,
submittedDate: submission.submittedDate,
status: submission.submissionStatus,
requestType: RequestAssessmentTypeAPIOutDTO.StudentAppeal,
}));
return submissions;
}

/**
* Get history of approved assessment requests and
* unsuccessful scholastic standings change requests(which will not create new assessment)
Expand Down Expand Up @@ -517,6 +544,7 @@ export class AssessmentControllerService {
offeringId: assessment.offering.id,
programId: assessment.offering.educationProgram.id,
studentAppealId: assessment.studentAppeal?.id,
formSubmissionId: assessment.formSubmission?.id,
applicationOfferingChangeRequestId:
assessment.applicationOfferingChangeRequest?.id,
applicationExceptionId: assessment.application.applicationException?.id,
Expand Down Expand Up @@ -600,6 +628,10 @@ export class AssessmentControllerService {
applicationId,
options?.studentId,
);
const formSubmissionAppeals = await this.getPendingAndDeniedStudentAppeals(
applicationId,
options?.studentId,
);
const applicationOfferingChangeRequests =
await this.getApplicationOfferingChangeRequestsByStatus(
applicationId,
Expand All @@ -613,6 +645,7 @@ export class AssessmentControllerService {
);
return requestAssessmentSummary
.concat(appeals)
.concat(formSubmissionAppeals)
.concat(applicationOfferingChangeRequests)
.sort(this.sortAssessmentHistory);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
AssessmentTriggerType,
COEStatus,
DisbursementScheduleStatus,
FormSubmissionStatus,
NOTE_DESCRIPTION_MAX_LENGTH,
OfferingIntensity,
OfferingStatus,
Expand Down Expand Up @@ -49,6 +50,7 @@ export enum RequestAssessmentTypeAPIOutDTO {

type RequestAssessmentSummaryStatus =
| StudentAppealStatus
| FormSubmissionStatus
| ApplicationExceptionStatus
| OfferingStatus
| ApplicationOfferingChangeRequestStatus;
Expand All @@ -70,6 +72,7 @@ export class AssessmentHistorySummaryAPIOutDTO {
offeringId?: number;
programId?: number;
studentAppealId?: number;
formSubmissionId?: number;
applicationOfferingChangeRequestId?: number;
applicationExceptionId?: number;
studentScholasticStandingId?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class FormSubmissionAESTController extends BaseController {
@Query("itemId", new ParseIntPipe({ optional: true })) itemId?: number,
): Promise<FormSubmissionMinistryAPIOutDTO> {
const submission =
await this.formSubmissionApprovalService.getFormSubmissionsById(
await this.formSubmissionApprovalService.getFormSubmissionById(
formSubmissionId,
{ itemId },
);
Expand Down Expand Up @@ -110,21 +110,24 @@ export class FormSubmissionAESTController extends BaseController {
dynamicFormConfigurationId: item.dynamicFormConfiguration.id,
submissionData: item.submittedData,
formDefinitionName: item.dynamicFormConfiguration.formDefinitionName,
decisionStatus:
item.currentDecision?.decisionStatus ??
FormSubmissionDecisionStatus.Pending,
updatedAt: item.updatedAt,
currentDecision:
hasApprovalAuthorization && item.currentDecision
? {
id: item.currentDecision.id,
decisionStatus: item.currentDecision.decisionStatus,
decisionStatus:
item.currentDecision?.decisionStatus ??
FormSubmissionDecisionStatus.Pending,
decisionDate: item.currentDecision.decisionDate,
decisionBy: getUserFullName(item.currentDecision.decisionBy),
decisionNoteDescription:
item.currentDecision.decisionNote.description,
}
: undefined,
: {
decisionStatus:
item.currentDecision?.decisionStatus ??
FormSubmissionDecisionStatus.Pending,
},
previousDecisions: hasApprovalAuthorization
? item.decisions
.filter((decision) => decision.id !== item.currentDecision.id)
Expand Down Expand Up @@ -152,7 +155,8 @@ export class FormSubmissionAESTController extends BaseController {
@ApiUnprocessableEntityResponse({
description:
"The form submission item has been updated since it was last retrieved or " +
"decisions cannot be made on items belonging to a form submission that is not pending.",
"decisions cannot be made on items belonging to a form submission that is not pending or " +
"the application associated with the form submission is not in completed status.",
})
@Roles(...FORM_SUBMISSION_UPDATE_ROLES)
@Patch("items/:formSubmissionItemId/decision")
Expand Down Expand Up @@ -204,7 +208,8 @@ export class FormSubmissionAESTController extends BaseController {
"the provided form submission items do not match the form submission items currently saved for this submission or " +
"form submission item not found in the form submission or " +
"form submission item has been updated since it was last retrieved or " +
"final decision cannot be made when some decisions are still pending.",
"final decision cannot be made when some decisions are still pending or " +
"the application associated with the form submission is not in completed status.",
})
@Roles(...FORM_SUBMISSION_UPDATE_ROLES)
@Patch(":formSubmissionId/complete")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { FormSubmissionService } from "../../services";
import {
FormSubmissionDecisionStatus,
FormSubmissionItem,
FormSubmissionStatus,
} from "@sims/sims-db";
import {
FormSubmissionAPIOutDTO,
FormSubmissionItemDecisionAPIOutDTO,
} from "./models/form-submission.dto";

@Injectable()
export class FormSubmissionControllerService {
constructor(private readonly formSubmissionService: FormSubmissionService) {}

/**
* Get the details of a form submission, including the individual form items and their details.
* @param formSubmissionId ID of the form submission to retrieve the details for.
* @param options.
* - `studentId`: optional ID used to validate the institution access to the student data.
* - `includeBasicDecisionDetails`: optional flag to include basic decision details, besides
* the decision status. Used for institutions to have access to more details than the student
* to better support them.
* @returns form submission details.
*/
async getFormSubmission(
formSubmissionId: number,
studentId: number,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't the studentId also be under options?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This method requires the student ID.

options?: {
includeBasicDecisionDetails?: boolean;
applicationId?: number;
},
): Promise<FormSubmissionAPIOutDTO> {
const submission = await this.formSubmissionService.getFormSubmissionById(
formSubmissionId,
studentId,
{ applicationId: options?.applicationId },
);
if (!submission) {
throw new NotFoundException(
`Form submission with ID ${formSubmissionId} not found.`,
);
}
return {
id: submission.id,
formCategory: submission.formCategory,
status: submission.submissionStatus,
applicationId: submission.application?.id,
applicationNumber: submission.application?.applicationNumber,
submittedDate: submission.submittedDate,
submissionItems: submission.formSubmissionItems.map((item) => ({
id: item.id,
formType: item.dynamicFormConfiguration.formType,
formCategory: item.dynamicFormConfiguration.formCategory,
dynamicFormConfigurationId: item.dynamicFormConfiguration.id,
submissionData: item.submittedData,
formDefinitionName: item.dynamicFormConfiguration.formDefinitionName,
currentDecision: this.mapCurrentDecision(
submission.submissionStatus,
item,
!!options?.includeBasicDecisionDetails,
),
})),
};
}

/**
* Define the decision to be returned.
* The decision and its details are determined based on the form submission status
* and the access to the decision details that the consumer has.
* @param submissionStatus form submission status.
* @param submissionItem form submission to determine the decision details to be returned.
* @param includeBasicDecisionDetails flag to indicate if the basic decision details should be included in the response,
* besides the status that is always included.
* @returns the decision that must be exposed the consumer.
*/
private mapCurrentDecision(
submissionStatus: FormSubmissionStatus,
submissionItem: FormSubmissionItem,
includeBasicDecisionDetails: boolean,
): FormSubmissionItemDecisionAPIOutDTO {
if (submissionStatus === FormSubmissionStatus.Pending) {
// For pending submissions, the decision details should not be returned.
return { decisionStatus: FormSubmissionDecisionStatus.Pending };
}
return {
decisionStatus: submissionItem.currentDecision.decisionStatus,
decisionNoteDescription: includeBasicDecisionDetails
? submissionItem.currentDecision.decisionNote.description
: undefined,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Controller, Get, Param, ParseIntPipe } from "@nestjs/common";
import { AuthorizedParties } from "../../auth";
import {
AllowAuthorizedParty,
HasStudentDataAccess,
IsBCPublicInstitution,
} from "../../auth/decorators";
import { ApiNotFoundResponse, ApiTags } from "@nestjs/swagger";
import BaseController from "../BaseController";
import { ClientTypeBaseRoute } from "../../types";
import { FormSubmissionControllerService } from "./form-submission.controller.service";
import { FormSubmissionAPIOutDTO } from "./models/form-submission.dto";

@AllowAuthorizedParty(AuthorizedParties.institution)
@IsBCPublicInstitution()
@Controller("form-submission")
@ApiTags(`${ClientTypeBaseRoute.Institution}-form-submission`)
export class FormSubmissionInstitutionsController extends BaseController {
constructor(
private readonly formSubmissionControllerService: FormSubmissionControllerService,
) {
super();
}

/**
* Get the details of a form submission, including the individual form items and their details.
* Please note currently the institution can only access form submissions related to their students
* and applications.
* @param formSubmissionId ID of the form submission to retrieve the details for.
* @param studentId student ID for authorization and to ensure the form submission belongs
* to the institution's student.
* @param applicationId application ID to ensure the institution has access to the
* student's application related to the form submission.
* @returns form submission details including individual form items and their details.
*/
@ApiNotFoundResponse({ description: "Form submission not found." })
@HasStudentDataAccess("studentId", "applicationId")
@Get(
"student/:studentId/application/:applicationId/form-submission/:formSubmissionId",
)
async getFormSubmission(
@Param("studentId", ParseIntPipe) studentId: number,
@Param("applicationId", ParseIntPipe) applicationId: number,
@Param("formSubmissionId", ParseIntPipe) formSubmissionId: number,
): Promise<FormSubmissionAPIOutDTO> {
return this.formSubmissionControllerService.getFormSubmission(
formSubmissionId,
studentId,
{ includeBasicDecisionDetails: true, applicationId },
);
}
}
Loading