Skip to content

Commit 231107b

Browse files
authored
#1978 - Request an Offering Change: Student View Request (#2196)
## **As a part of this PR, the follow tasks were completed:** - [x] Added a view request link in requests and history assessment table which redirects to view request page. - [x] Added the logic to the "Review changes" button to redirect to the view request page. - [x] On the view request page, added tabs for "Requested change" and "Active application details". - [x] Added migration scripts to add `student_consent` column in the `application_offering_change_requests` relation in the database. - [x] Created modal with SABC declaration when "Allow change" is clicked. - [x] Created modal to decline the application offering change request when "Decline change" is clicked. - [x] Created migration scripts to add the declaration attribute to the application offering change request entity. - [x] Added toast message for "allow" and "decline" scenarios -- "Your decision has been submitted successfully". - [x] Renamed the "Active application details" tab to "Previous application details" in the view request page when the ministry approves the application offering change request. - [x] Enabled the route redirection back to the application tracker after the student submits the application offering change request. - [x] Removed the "Decline change" and "Allow change" buttons when student submits the application offering change request decision. - [x] The logic for the below scenarios was adjusted: 1. Text under the "Application Details" and "Request Details" headings is only displayed when the application offering change request is "In progress with student." 2. Program costs are not shown for the students. 3. Status next to the student name reflects the feature status. # **Screenshots** ### View Request Link in the Requests and History Tables <img width="1918" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/f9ee51f9-ac00-4877-b3c5-3540ed7b670e"> ### "Requested change" tab <img width="1917" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/e1910580-d2fa-4b09-af3d-9a49fe7b75ed"> ### "Active application details" tab <img width="1917" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/0bf67b82-13c1-4785-b85b-b6db234db69f"> ### Decline Application Offering Change Request Modal <img width="1916" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/2587e3c9-e3c2-450c-8fc0-88836cee5e3c"> ### Approve Application Offering Change Request Modal <img width="1914" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/67fce378-90d5-441d-a3d3-833c848e9e97"> ### Application Offering Change Request in progress with SABC <img width="1916" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/4ea29b39-8e9e-4d12-bde6-d3641b7c49cc"> ### Application Offering Change Request Approved by SABC <img width="1914" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/60ccff31-e20a-4e54-9189-7077facc8605"> ### Validation Error in the Approve Application Modal Dialog when the student consent is not given <img width="1917" alt="image" src="https://github.com/bcgov/SIMS/assets/7859295/a43c60d0-7929-424f-8a1b-fffb3b51768a"> ### **Note:** As per discussion with the team, I have not created a common component for the two tabs: "Requested details" and "Active application details". ## **Next PR:** - [ ] **e2e tests**
1 parent ece1045 commit 231107b

File tree

47 files changed

+1385
-85
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1385
-85
lines changed

sources/packages/backend/apps/api/src/app.institutions.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
NoteInstitutionsController,
6767
RestrictionControllerService,
6868
ApplicationOfferingChangeRequestInstitutionsController,
69+
ApplicationOfferingChangeRequestControllerService,
6970
} from "./route-controllers";
7071
import { AuthModule } from "./auth/auth.module";
7172
import {
@@ -163,6 +164,7 @@ import {
163164
ApplicationExceptionControllerService,
164165
StudentAppealControllerService,
165166
ApplicationOfferingChangeRequestService,
167+
ApplicationOfferingChangeRequestControllerService,
166168
ApplicationWithdrawalImportTextService,
167169
],
168170
})

sources/packages/backend/apps/api/src/app.students.module.ts

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
EducationProgramStudentsController,
3636
StudentAccountApplicationStudentsController,
3737
EducationProgramOfferingStudentsController,
38+
ApplicationOfferingChangeRequestStudentsController,
39+
ApplicationOfferingChangeRequestControllerService,
3840
EducationProgramOfferingControllerService,
3941
RestrictionStudentsController,
4042
ProgramYearStudentsController,
@@ -77,6 +79,7 @@ import { ATBCIntegrationModule } from "@sims/integrations/atbc-integration";
7779
ATBCStudentController,
7880
ProgramYearStudentsController,
7981
OverawardStudentsController,
82+
ApplicationOfferingChangeRequestStudentsController,
8083
],
8184
providers: [
8285
WorkflowClientService,
@@ -117,6 +120,7 @@ import { ATBCIntegrationModule } from "@sims/integrations/atbc-integration";
117120
OverawardControllerService,
118121
StudentAppealControllerService,
119122
ConfirmationOfEnrollmentService,
123+
ApplicationOfferingChangeRequestControllerService,
120124
ApplicationOfferingChangeRequestService,
121125
],
122126
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Injectable, NotFoundException } from "@nestjs/common";
2+
import { ApplicationOfferingChangeRequestService } from "../../services";
3+
import {
4+
ApplicationOfferingChangesAPIOutDTO,
5+
ApplicationOfferingDetailsAPIOutDTO,
6+
} from "./models/application-offering-change-request.dto";
7+
import { getUserFullName } from "../../utilities";
8+
9+
@Injectable()
10+
export class ApplicationOfferingChangeRequestControllerService {
11+
constructor(
12+
private readonly applicationOfferingChangeRequestService: ApplicationOfferingChangeRequestService,
13+
) {}
14+
15+
/**
16+
* Get the Application Offering Change Request by its id for the institution.
17+
* @param applicationOfferingChangeRequestId the Application Offering Change Request id.
18+
* @param options method options:
19+
* - `locationId`: location id for institution authorization.
20+
* @returns application offering change request.
21+
*/
22+
async getApplicationOfferingChangeRequest(
23+
applicationOfferingChangeRequestId: number,
24+
options: {
25+
locationId: number;
26+
},
27+
): Promise<ApplicationOfferingChangesAPIOutDTO>;
28+
29+
/**
30+
* Get the Application Offering Change Request by its id for the student.
31+
* @param applicationOfferingChangeRequestId the Application Offering Change Request id.
32+
* @param options method options:
33+
* - `studentId`: student id for student authorization.
34+
* - `applicationOfferingDetails`: boolean true indicates that only the required application offering details for the student are requested.
35+
* @returns application offering change request.
36+
*/
37+
async getApplicationOfferingChangeRequest(
38+
applicationOfferingChangeRequestId: number,
39+
options: {
40+
studentId: number;
41+
applicationOfferingDetails: boolean;
42+
},
43+
): Promise<ApplicationOfferingDetailsAPIOutDTO>;
44+
45+
/**
46+
* Get the Application Offering Change Request by its id.
47+
* @param applicationOfferingChangeRequestId the Application Offering Change Request id.
48+
* @param options method options:
49+
* - `studentId`: student id for student authorization.
50+
* - `locationId`: location id for institution authorization.
51+
* - `applicationOfferingDetails`: boolean true indicates that only the required application offering details for the student are requested.
52+
* @returns application offering change request.
53+
*/
54+
async getApplicationOfferingChangeRequest(
55+
applicationOfferingChangeRequestId: number,
56+
options: {
57+
studentId?: number;
58+
locationId?: number;
59+
applicationOfferingDetails?: boolean;
60+
},
61+
): Promise<
62+
ApplicationOfferingChangesAPIOutDTO | ApplicationOfferingDetailsAPIOutDTO
63+
> {
64+
const request = await this.applicationOfferingChangeRequestService.getById(
65+
applicationOfferingChangeRequestId,
66+
{ studentId: options?.studentId, locationId: options?.locationId },
67+
);
68+
if (!request) {
69+
throw new NotFoundException(
70+
"Not able to find an Application Offering Change Request.",
71+
);
72+
}
73+
const applicationOfferingDetails = {
74+
status: request.applicationOfferingChangeRequestStatus,
75+
applicationNumber: request.application.applicationNumber,
76+
locationName: request.application.location.name,
77+
requestedOfferingId: request.requestedOffering.id,
78+
activeOfferingId: request.activeOffering.id,
79+
reason: request.reason,
80+
};
81+
if (options?.applicationOfferingDetails) {
82+
return applicationOfferingDetails;
83+
}
84+
return {
85+
...applicationOfferingDetails,
86+
id: request.id,
87+
applicationId: request.application.id,
88+
requestedOfferingDescription: request.requestedOffering.name,
89+
requestedOfferingProgramId: request.requestedOffering.educationProgram.id,
90+
requestedOfferingProgramName:
91+
request.requestedOffering.educationProgram.name,
92+
assessedNoteDescription: request.assessedNote?.description,
93+
studentFullName: getUserFullName(request.application.student.user),
94+
};
95+
}
96+
}

sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts

+4-23
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
OFFERING_PROGRAM_YEAR_MISMATCH,
5454
} from "../../constants";
5555
import { InjectLogger, LoggerService } from "@sims/utilities/logger";
56+
import { ApplicationOfferingChangeRequestControllerService } from "./application-offering-change-request.controller.service";
5657

5758
/**
5859
* Application offering change request controller for institutions client.
@@ -65,6 +66,7 @@ import { InjectLogger, LoggerService } from "@sims/utilities/logger";
6566
@HasLocationAccess("locationId")
6667
export class ApplicationOfferingChangeRequestInstitutionsController extends BaseController {
6768
constructor(
69+
private readonly applicationOfferingChangeRequestControllerService: ApplicationOfferingChangeRequestControllerService,
6870
private readonly applicationOfferingChangeRequestService: ApplicationOfferingChangeRequestService,
6971
private readonly applicationService: ApplicationService,
7072
) {
@@ -238,36 +240,15 @@ export class ApplicationOfferingChangeRequestInstitutionsController extends Base
238240
@ApiNotFoundResponse({
239241
description: "Not able to find an Application Offering Change Request.",
240242
})
241-
async getById(
243+
async getApplicationOfferingChangeRequest(
242244
@Param("applicationOfferingChangeRequestId", ParseIntPipe)
243245
applicationOfferingChangeRequestId: number,
244246
@Param("locationId", ParseIntPipe) locationId: number,
245247
): Promise<ApplicationOfferingChangesAPIOutDTO> {
246-
const request = await this.applicationOfferingChangeRequestService.getById(
248+
return this.applicationOfferingChangeRequestControllerService.getApplicationOfferingChangeRequest(
247249
applicationOfferingChangeRequestId,
248250
{ locationId },
249251
);
250-
if (!request) {
251-
throw new NotFoundException(
252-
"Not able to find an Application Offering Change Request.",
253-
);
254-
}
255-
return {
256-
id: request.id,
257-
status: request.applicationOfferingChangeRequestStatus,
258-
applicationId: request.application.id,
259-
applicationNumber: request.application.applicationNumber,
260-
locationName: request.application.location.name,
261-
activeOfferingId: request.activeOffering.id,
262-
requestedOfferingId: request.requestedOffering.id,
263-
requestedOfferingDescription: request.requestedOffering.name,
264-
requestedOfferingProgramId: request.requestedOffering.educationProgram.id,
265-
requestedOfferingProgramName:
266-
request.requestedOffering.educationProgram.name,
267-
reason: request.reason,
268-
assessedNoteDescription: request.assessedNote?.description,
269-
studentFullName: getUserFullName(request.application.student.user),
270-
};
271252
}
272253

273254
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import {
2+
Body,
3+
Controller,
4+
Get,
5+
NotFoundException,
6+
Param,
7+
ParseIntPipe,
8+
Patch,
9+
UnprocessableEntityException,
10+
} from "@nestjs/common";
11+
import {
12+
ApiNotFoundResponse,
13+
ApiTags,
14+
ApiUnprocessableEntityResponse,
15+
} from "@nestjs/swagger";
16+
import { AuthorizedParties } from "../../auth/authorized-parties.enum";
17+
import { AllowAuthorizedParty, UserToken } from "../../auth/decorators";
18+
import { ClientTypeBaseRoute } from "../../types";
19+
import BaseController from "../BaseController";
20+
import {
21+
ApplicationOfferingChangeRequestStatusAPIOutDTO,
22+
ApplicationOfferingDetailsAPIOutDTO,
23+
StudentApplicationOfferingChangeRequestAPIInDTO,
24+
} from "./models/application-offering-change-request.dto";
25+
import { StudentUserToken } from "../../auth";
26+
import { ApplicationOfferingChangeRequestControllerService } from "./application-offering-change-request.controller.service";
27+
import { ApplicationOfferingChangeRequestService } from "../../services";
28+
import { ApplicationOfferingChangeRequestStatus } from "@sims/sims-db";
29+
30+
/**
31+
* Application offering change request controller for students client.
32+
*/
33+
@AllowAuthorizedParty(AuthorizedParties.student)
34+
@Controller("application-offering-change-request")
35+
@ApiTags(`${ClientTypeBaseRoute.Student}-application-offering-change-request`)
36+
export class ApplicationOfferingChangeRequestStudentsController extends BaseController {
37+
constructor(
38+
private readonly applicationOfferingChangeRequestService: ApplicationOfferingChangeRequestService,
39+
private readonly applicationOfferingChangeRequestControllerService: ApplicationOfferingChangeRequestControllerService,
40+
) {
41+
super();
42+
}
43+
44+
/**
45+
* Gets the Application Offering Change Request details.
46+
* @param applicationOfferingChangeRequestId the Application Offering Change Request id.
47+
* @param studentUserToken student user token to authorize the user.
48+
* @returns Application Offering Change Request details.
49+
*/
50+
@Get(":applicationOfferingChangeRequestId")
51+
@ApiNotFoundResponse({
52+
description: "Not able to find an Application Offering Change Request.",
53+
})
54+
async getApplicationOfferingChangeRequest(
55+
@Param("applicationOfferingChangeRequestId", ParseIntPipe)
56+
applicationOfferingChangeRequestId: number,
57+
@UserToken()
58+
studentUserToken: StudentUserToken,
59+
): Promise<ApplicationOfferingDetailsAPIOutDTO> {
60+
return this.applicationOfferingChangeRequestControllerService.getApplicationOfferingChangeRequest(
61+
applicationOfferingChangeRequestId,
62+
{
63+
studentId: studentUserToken.studentId,
64+
applicationOfferingDetails: true,
65+
},
66+
);
67+
}
68+
69+
/**
70+
* Gets the Application Offering Change Request status.
71+
* @param applicationOfferingChangeRequestId the Application Offering Change Request id.
72+
* @param studentUserToken student user token to authorize the user.
73+
* @returns Application Offering Change Request status.
74+
*/
75+
@Get(
76+
":applicationOfferingChangeRequestId/application-offering-change-request-status",
77+
)
78+
@ApiNotFoundResponse({
79+
description:
80+
"Not able to get the Application Offering Change Request Status.",
81+
})
82+
async getApplicationOfferingChangeRequestStatus(
83+
@Param("applicationOfferingChangeRequestId", ParseIntPipe)
84+
applicationOfferingChangeRequestId: number,
85+
@UserToken()
86+
studentUserToken: StudentUserToken,
87+
): Promise<ApplicationOfferingChangeRequestStatusAPIOutDTO> {
88+
const applicationOfferingChangeRequestStatus =
89+
await this.applicationOfferingChangeRequestService.getApplicationOfferingChangeRequestStatusById(
90+
applicationOfferingChangeRequestId,
91+
{
92+
studentId: studentUserToken.studentId,
93+
},
94+
);
95+
if (!applicationOfferingChangeRequestStatus) {
96+
throw new NotFoundException(
97+
"Application Offering Change Request not found.",
98+
);
99+
}
100+
return { status: applicationOfferingChangeRequestStatus };
101+
}
102+
103+
/**
104+
* Updates an application offering change request status.
105+
* @param applicationOfferingChangeRequestId application offering change request id.
106+
* @param userToken user token to authorize the user.
107+
* @param payload information to update the application offering change request status.
108+
*/
109+
@Patch(":applicationOfferingChangeRequestId")
110+
@ApiNotFoundResponse({
111+
description:
112+
"Application offering change not found or not in valid status to be updated.",
113+
})
114+
@ApiUnprocessableEntityResponse({
115+
description:
116+
"Invalid application offering change status or student consent not provided",
117+
})
118+
async updateApplicationOfferingChangeRequestStatus(
119+
@Param("applicationOfferingChangeRequestId", ParseIntPipe)
120+
applicationOfferingChangeRequestId: number,
121+
@UserToken()
122+
userToken: StudentUserToken,
123+
@Body()
124+
payload: StudentApplicationOfferingChangeRequestAPIInDTO,
125+
): Promise<void> {
126+
const applicationOfferingChangeRequest =
127+
await this.applicationOfferingChangeRequestService.applicationOfferingChangeRequestExists(
128+
applicationOfferingChangeRequestId,
129+
{
130+
applicationOfferingChangeRequestStatus:
131+
ApplicationOfferingChangeRequestStatus.InProgressWithStudent,
132+
studentId: userToken.studentId,
133+
},
134+
);
135+
if (!applicationOfferingChangeRequest) {
136+
throw new NotFoundException(
137+
"Application offering change not found or not in valid status to be updated.",
138+
);
139+
}
140+
if (
141+
!(
142+
payload.applicationOfferingChangeRequestStatus ===
143+
ApplicationOfferingChangeRequestStatus.InProgressWithSABC ||
144+
payload.applicationOfferingChangeRequestStatus ===
145+
ApplicationOfferingChangeRequestStatus.DeclinedByStudent
146+
)
147+
) {
148+
throw new UnprocessableEntityException(
149+
"Invalid application offering change request status.",
150+
);
151+
}
152+
if (
153+
payload.applicationOfferingChangeRequestStatus ===
154+
ApplicationOfferingChangeRequestStatus.InProgressWithSABC &&
155+
!payload.studentConsent
156+
) {
157+
throw new UnprocessableEntityException(
158+
"Student consent is required to update the application offering change request status.",
159+
);
160+
}
161+
await this.applicationOfferingChangeRequestService.updateApplicationOfferingChangeRequest(
162+
applicationOfferingChangeRequestId,
163+
payload.applicationOfferingChangeRequestStatus,
164+
payload.studentConsent,
165+
);
166+
}
167+
}

0 commit comments

Comments
 (0)