-
Notifications
You must be signed in to change notification settings - Fork 14
#5816 - Appeals/Forms history view, data connection, and institution view - History #5873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b193afc
30dc92f
67fae34
e8db8f7
0017e6b
306de6a
1bfce9c
4895d70
ef89da6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ import { | |
| FormSubmissionItemDecisionAPIInDTO, | ||
| FormSubmissionMinistryAPIOutDTO, | ||
| FormSubmissionPendingSummaryAPIOutDTO, | ||
| FormSubmissionsAPIOutDTO, | ||
| } from "./models/form-submission.dto"; | ||
| import { getUserFullName } from "../../utilities"; | ||
| import { FormSubmissionDecisionStatus } from "@sims/sims-db"; | ||
|
|
@@ -46,6 +47,7 @@ import { | |
| FormSubmissionPendingPaginationOptionsAPIInDTO, | ||
| PaginatedResultsAPIOutDTO, | ||
| } from "../models/pagination.dto"; | ||
| import { FormSubmissionControllerService } from "./form-submission.controller.service"; | ||
|
|
||
| /** | ||
| * Roles allowed to update the form submission item decision | ||
|
|
@@ -64,6 +66,7 @@ export class FormSubmissionAESTController extends BaseController { | |
| constructor( | ||
| private readonly formSubmissionApprovalService: FormSubmissionApprovalService, | ||
| private readonly formSubmissionService: FormSubmissionService, | ||
| private readonly formSubmissionControllerService: FormSubmissionControllerService, | ||
| ) { | ||
| super(); | ||
| } | ||
|
|
@@ -95,6 +98,28 @@ export class FormSubmissionAESTController extends BaseController { | |
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the list of form submissions for a student, | ||
| * including the individual form items and their details. | ||
| * @param studentId student ID to retrieve the form submission history for. | ||
| * @returns list of form submissions for a student. | ||
| */ | ||
| @Get("student/:studentId") | ||
| async getFormSubmissionHistory( | ||
| @Param("studentId", ParseIntPipe) studentId: number, | ||
| ): Promise<FormSubmissionsAPIOutDTO> { | ||
| // Kept the includeBasicDecisionDetails as false since the details controlled by | ||
| // the flag are not required to be returned by this endpoint. | ||
| const submissions = | ||
| await this.formSubmissionControllerService.getFormSubmissions(studentId, { | ||
| includeBasicDecisionDetails: false, | ||
| keepPendingDecisionsWhilePendingFormSubmission: false, | ||
|
Comment on lines
+115
to
+116
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it not as good as not passing the options?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comments and minor refactor to |
||
| }); | ||
| return { | ||
| submissions, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * 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. | ||
|
|
@@ -109,11 +134,10 @@ export class FormSubmissionAESTController extends BaseController { | |
| @Param("formSubmissionId", ParseIntPipe) formSubmissionId: number, | ||
| @Query("itemId", new ParseIntPipe({ optional: true })) itemId?: number, | ||
| ): Promise<FormSubmissionMinistryAPIOutDTO> { | ||
| const submission = | ||
| await this.formSubmissionApprovalService.getFormSubmissionById( | ||
| formSubmissionId, | ||
| { itemId }, | ||
| ); | ||
| const [submission] = await this.formSubmissionService.getFormSubmissions( | ||
| { formSubmissionId, itemId }, | ||
| { includeDecisionHistory: true, loadSubmittedData: true }, | ||
| ); | ||
| if (!submission) { | ||
| if (itemId) { | ||
| throw new NotFoundException( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { Injectable, NotFoundException } from "@nestjs/common"; | ||
| import { FormSubmissionService } from "../../services"; | ||
| import { | ||
| FormSubmission, | ||
| FormSubmissionDecisionStatus, | ||
| FormSubmissionItem, | ||
| FormSubmissionStatus, | ||
|
|
@@ -16,40 +17,91 @@ export class FormSubmissionControllerService { | |
|
|
||
| /** | ||
| * 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 studentId ID used to validate the access to the student data. | ||
| * @param studentId ID of the student to have the data retrieved. | ||
| * @param options. | ||
| * - `includeBasicDecisionDetails`: optional flag to include basic decision details, besides | ||
| * - `formSubmissionId` allow searching for a specific form submission. When provided, it will validate if the form | ||
| * submission belongs to the student and throw a not found HTTP error if it does not. | ||
| * - `locationIds` restrict forms with an application scope to the provided locations. Used for institutions to have access | ||
| * only to the form submissions related to the locations they have access to. | ||
| * - `keepPendingDecisionsWhilePendingFormSubmission`, when true, will return "Pending" as the decision status for all items | ||
| * if the form submission is still pending. This is used to avoid showing decisions that are not final yet while the form | ||
| * submission is not completed. Default to true when not provided to expose less information. | ||
| * - `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. | ||
| * - `applicationId`: optional ID of the application, used to validate the access to the form submission | ||
| * @returns form submission details. | ||
| * to better support them. Default to false when not provided to expose less information. When keepPendingDecisionsWhilePendingFormSubmission | ||
| * is true, the decision details will not be included while the form submission is pending to avoid showing non-final decisions | ||
| * to be exposed. | ||
| * - `loadSubmittedData` includes the submitted data of each form item. | ||
| * @returns form submission details including individual form items and their details. | ||
| * @throws NotFoundException when the formSubmissionId is provided but no record is returned. | ||
| */ | ||
| async getFormSubmission( | ||
| formSubmissionId: number, | ||
| async getFormSubmissions( | ||
| studentId: number, | ||
| options?: { | ||
| formSubmissionId?: number; | ||
| locationIds?: number[]; | ||
| keepPendingDecisionsWhilePendingFormSubmission?: boolean; | ||
| includeBasicDecisionDetails?: boolean; | ||
| applicationId?: number; | ||
| loadSubmittedData?: boolean; | ||
| }, | ||
| ): Promise<FormSubmissionAPIOutDTO> { | ||
| const submission = await this.formSubmissionService.getFormSubmissionById( | ||
| formSubmissionId, | ||
| studentId, | ||
| { applicationId: options?.applicationId }, | ||
| ): Promise<FormSubmissionAPIOutDTO[]> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should you be using FormSubmissionsAPIOutDTO?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This controller service is used by API endpoints that return one or multiple records.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thats fine, i just spotted the different returns, i guess my confusion was why have a separate FormSubmissionsAPIOutDTO and not use the array FormSubmissionAPIOutDTO[] directly?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We try to avoid using the direct array return as a pattern because what is an array today may be a property in the DTO that would need to be expanded in the future. |
||
| const submissions = await this.formSubmissionService.getFormSubmissions( | ||
| { studentId, formSubmissionId: options?.formSubmissionId }, | ||
| { | ||
| locationIds: options?.locationIds, | ||
| loadSubmittedData: options?.loadSubmittedData, | ||
| }, | ||
| ); | ||
| if (!submission) { | ||
| if (options?.formSubmissionId && !submissions?.length) { | ||
| throw new NotFoundException( | ||
| `Form submission with ID ${formSubmissionId} not found.`, | ||
| `Form submission with ID ${options?.formSubmissionId} not found.`, | ||
| ); | ||
| } | ||
|
|
||
| // Set default value for the options that define how data will be returned considering the | ||
| // default behavior to expose less information and avoid showing non-final decisions. | ||
| const keepPendingDecisionsWhilePendingFormSubmission = | ||
| options?.keepPendingDecisionsWhilePendingFormSubmission ?? true; | ||
| let includeBasicDecisionDetails: boolean; | ||
| if (keepPendingDecisionsWhilePendingFormSubmission) { | ||
| includeBasicDecisionDetails = false; | ||
| } else { | ||
| includeBasicDecisionDetails = | ||
| options?.includeBasicDecisionDetails ?? false; | ||
| } | ||
| return submissions.map((submission) => | ||
| this.mapSubmissionsToAPIOutDTO( | ||
| submission, | ||
| includeBasicDecisionDetails, | ||
| keepPendingDecisionsWhilePendingFormSubmission, | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Convert a form submission record to the API output format, | ||
| * including the individual form items and their details. | ||
| * @param submission form submission record to be converted. | ||
| * @param includeBasicDecisionDetails flag to indicate if the basic decision details should be included in the response, | ||
| * besides the status that is always included. | ||
| * @param keepPendingDecisionsWhilePendingFormSubmission when true, will return "Pending" as the decision status for all items | ||
| * if the form submission is still pending. This is used to avoid showing decisions that are not final yet while the form | ||
| * submission is not completed. | ||
| * @returns form submission details including individual form items and their details in the API output format. | ||
| */ | ||
| private mapSubmissionsToAPIOutDTO( | ||
| submission: FormSubmission, | ||
| includeBasicDecisionDetails: boolean, | ||
| keepPendingDecisionsWhilePendingFormSubmission: boolean, | ||
| ): FormSubmissionAPIOutDTO { | ||
| return { | ||
| id: submission.id, | ||
| formCategory: submission.formCategory, | ||
| status: submission.submissionStatus, | ||
| applicationId: submission.application?.id, | ||
| applicationNumber: submission.application?.applicationNumber, | ||
| submittedDate: submission.submittedDate, | ||
| assessedDate: submission.assessedDate, | ||
| submissionItems: submission.formSubmissionItems.map((item) => ({ | ||
| id: item.id, | ||
| formType: item.dynamicFormConfiguration.formType, | ||
|
|
@@ -60,7 +112,8 @@ export class FormSubmissionControllerService { | |
| currentDecision: this.mapCurrentDecision( | ||
| submission.submissionStatus, | ||
| item, | ||
| !!options?.includeBasicDecisionDetails, | ||
| includeBasicDecisionDetails, | ||
| keepPendingDecisionsWhilePendingFormSubmission, | ||
| ), | ||
| })), | ||
| }; | ||
|
|
@@ -80,15 +133,19 @@ export class FormSubmissionControllerService { | |
| submissionStatus: FormSubmissionStatus, | ||
| submissionItem: FormSubmissionItem, | ||
| includeBasicDecisionDetails: boolean, | ||
| keepPendingDecisionsWhilePendingFormSubmission: boolean, | ||
| ): FormSubmissionItemDecisionAPIOutDTO { | ||
| if (submissionStatus === FormSubmissionStatus.Pending) { | ||
| // For pending submissions, the decision details should not be returned. | ||
| return { decisionStatus: FormSubmissionDecisionStatus.Pending }; | ||
| } | ||
| let decisionStatus = | ||
| keepPendingDecisionsWhilePendingFormSubmission && | ||
| submissionStatus === FormSubmissionStatus.Pending | ||
| ? FormSubmissionDecisionStatus.Pending | ||
| : submissionItem.currentDecision?.decisionStatus; | ||
| // Default to Pending if no decision exists. | ||
| decisionStatus = decisionStatus ?? FormSubmissionDecisionStatus.Pending; | ||
| return { | ||
| decisionStatus: submissionItem.currentDecision.decisionStatus, | ||
| decisionStatus, | ||
| decisionNoteDescription: includeBasicDecisionDetails | ||
| ? submissionItem.currentDecision.decisionNote.description | ||
| ? submissionItem.currentDecision?.decisionNote?.description | ||
| : undefined, | ||
andrewsignori-aot marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,23 @@ | ||
| import { Controller, Get, Param, ParseIntPipe } from "@nestjs/common"; | ||
| import { AuthorizedParties } from "../../auth"; | ||
| import { AuthorizedParties, IInstitutionUserToken } from "../../auth"; | ||
| import { | ||
| AllowAuthorizedParty, | ||
| HasStudentDataAccess, | ||
| IsBCPublicInstitution, | ||
| UserToken, | ||
| } 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"; | ||
| import { | ||
| FormSubmissionAPIOutDTO, | ||
| FormSubmissionsAPIOutDTO, | ||
| } from "./models/form-submission.dto"; | ||
|
|
||
| @AllowAuthorizedParty(AuthorizedParties.institution) | ||
| @IsBCPublicInstitution() | ||
| @HasStudentDataAccess("studentId") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While using the location ids, isn't this check redundant?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| @Controller("form-submission") | ||
| @ApiTags(`${ClientTypeBaseRoute.Institution}-form-submission`) | ||
| export class FormSubmissionInstitutionsController extends BaseController { | ||
|
|
@@ -22,6 +27,27 @@ export class FormSubmissionInstitutionsController extends BaseController { | |
| super(); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the list of form submissions for a student, including the individual form items and their details. | ||
| * The form submissions with application scope will be restricted to the locations the user has access. | ||
| * All form submissions without application scope can be retrieved as long as the user has access to the student data. | ||
| * @param studentId student ID to retrieve the form submission history for. | ||
| * @returns list of form submissions for a student. | ||
| */ | ||
| @Get("student/:studentId") | ||
| async getFormSubmissionHistory( | ||
| @Param("studentId", ParseIntPipe) studentId: number, | ||
| @UserToken() userToken: IInstitutionUserToken, | ||
| ): Promise<FormSubmissionsAPIOutDTO> { | ||
andrewsignori-aot marked this conversation as resolved.
Show resolved
Hide resolved
andrewsignori-aot marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const submissions = | ||
| await this.formSubmissionControllerService.getFormSubmissions(studentId, { | ||
| locationIds: userToken.authorizations.getLocationsIds(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| }); | ||
| return { | ||
| submissions, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * 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 | ||
|
|
@@ -34,19 +60,19 @@ export class FormSubmissionInstitutionsController extends BaseController { | |
| * @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", | ||
| ) | ||
| @Get("student/:studentId/form-submission/:formSubmissionId") | ||
| async getFormSubmission( | ||
andrewsignori-aot marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @Param("studentId", ParseIntPipe) studentId: number, | ||
| @Param("applicationId", ParseIntPipe) applicationId: number, | ||
| @Param("formSubmissionId", ParseIntPipe) formSubmissionId: number, | ||
| @UserToken() userToken: IInstitutionUserToken, | ||
| ): Promise<FormSubmissionAPIOutDTO> { | ||
| return this.formSubmissionControllerService.getFormSubmission( | ||
| formSubmissionId, | ||
| studentId, | ||
| { includeBasicDecisionDetails: true, applicationId }, | ||
| ); | ||
| const [submission] = | ||
| await this.formSubmissionControllerService.getFormSubmissions(studentId, { | ||
| formSubmissionId, | ||
| includeBasicDecisionDetails: true, | ||
| loadSubmittedData: true, | ||
| locationIds: userToken.authorizations.getLocationsIds(), | ||
| }); | ||
| return submission; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.