Skip to content

Commit 007adef

Browse files
#1786 - Enable Search Student for Institutions - part 1 (#1857)
* initial commit * removed unused import * changed SearchStudents from ministry to use the same component * reverted conf to display queries on logs * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues * review issues
1 parent 79c3270 commit 007adef

File tree

20 files changed

+413
-216
lines changed

20 files changed

+413
-216
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import {
4242
EducationProgramOfferingInstitutionsController,
4343
EducationProgramOfferingControllerService,
4444
ConfirmationOfEnrollmentControllerService,
45+
StudentInstitutionsController,
46+
StudentControllerService,
4547
} from "./route-controllers";
4648
import { AuthModule } from "./auth/auth.module";
4749
import {
@@ -73,6 +75,7 @@ import { UserControllerService } from "./route-controllers/user/user.controller.
7375
ProgramInfoRequestInstitutionsController,
7476
EducationProgramOfferingInstitutionsController,
7577
UserInstitutionsController,
78+
StudentInstitutionsController,
7679
],
7780
providers: [
7881
WorkflowClientService,
@@ -117,6 +120,7 @@ import { UserControllerService } from "./route-controllers/user/user.controller.
117120
ConfirmationOfEnrollmentService,
118121
DisbursementOverawardService,
119122
NoteSharedService,
123+
StudentControllerService,
120124
],
121125
})
122126
export class AppInstitutionsModule {}

sources/packages/backend/apps/api/src/route-controllers/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ export * from "./confirmation-of-enrollment/confirmation-of-enrollment.aest.cont
5656
export * from "./overaward/overaward.controller.service";
5757
export * from "./overaward/overaward.aest.controller";
5858
export * from "./overaward/overaward.students.controller";
59+
export * from "./student/student.institutions.controller";

sources/packages/backend/apps/api/src/route-controllers/institution/institution.controller.service.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { InstitutionService, InstitutionTypeService } from "../../services";
33
import { AddressInfo } from "@sims/sims-db";
44
import { InstitutionDetailAPIOutDTO } from "./models/institution.dto";
55
import { OptionItemAPIOutDTO } from "../models/common.dto";
6-
import { INSTITUTION_TYPE_BC_PRIVATE } from "@sims/sims-db/constant";
6+
import {
7+
INSTITUTION_TYPE_BC_PRIVATE,
8+
INSTITUTION_TYPE_BC_PUBLIC,
9+
} from "@sims/sims-db/constant";
710

811
/**
912
* Service/Provider for Institutions controller to wrap the common methods.
@@ -31,6 +34,8 @@ export class InstitutionControllerService {
3134
}
3235
const isBCPrivate =
3336
INSTITUTION_TYPE_BC_PRIVATE === institutionDetail.institutionType.id;
37+
const isBCPublic =
38+
INSTITUTION_TYPE_BC_PUBLIC === institutionDetail.institutionType.id;
3439

3540
// {} as AddressInfo is added to prevent old data to break.
3641
const mailingAddress =
@@ -62,7 +67,8 @@ export class InstitutionControllerService {
6267
postalCode: mailingAddress.postalCode,
6368
selectedCountry: mailingAddress.selectedCountry,
6469
},
65-
isBCPrivate: isBCPrivate,
70+
isBCPrivate,
71+
isBCPublic,
6672
hasBusinessGuid: !!institutionDetail.businessGuid,
6773
};
6874
}

sources/packages/backend/apps/api/src/route-controllers/institution/models/institution.dto.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ export class InstitutionProfileAPIOutDTO extends InstitutionContactAPIOutDTO {
131131
export class InstitutionDetailAPIOutDTO extends InstitutionProfileAPIOutDTO {
132132
legalOperatingName: string;
133133
institutionTypeName?: string;
134-
isBCPrivate?: boolean;
134+
isBCPrivate: boolean;
135+
isBCPublic: boolean;
135136
/**
136137
* Indicates if the institution has a BCeID business guid
137138
* associated with, if not it is a basic BCeID institution.

sources/packages/backend/apps/api/src/route-controllers/student/models/student.dto.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -91,29 +91,29 @@ export class UpdateStudentAPIInDTO
9191
}
9292

9393
/**
94-
* Student AEST search parameters.
94+
* Student search parameters.
9595
*/
96-
export class AESTStudentSearchAPIInDTO {
96+
export class StudentSearchAPIInDTO {
9797
@ValidateIf(
98-
(input: AESTStudentSearchAPIInDTO) =>
98+
(input: StudentSearchAPIInDTO) =>
9999
!input.lastName && !input.appNumber && !input.sin,
100100
)
101101
@IsNotEmpty()
102102
firstName: string;
103103
@ValidateIf(
104-
(input: AESTStudentSearchAPIInDTO) =>
104+
(input: StudentSearchAPIInDTO) =>
105105
!input.firstName && !input.appNumber && !input.sin,
106106
)
107107
@IsNotEmpty()
108108
lastName: string;
109109
@ValidateIf(
110-
(input: AESTStudentSearchAPIInDTO) =>
110+
(input: StudentSearchAPIInDTO) =>
111111
!input.firstName && !input.lastName && !input.sin,
112112
)
113113
@IsNotEmpty()
114114
appNumber: string;
115115
@ValidateIf(
116-
(input: AESTStudentSearchAPIInDTO) =>
116+
(input: StudentSearchAPIInDTO) =>
117117
!input.firstName && !input.lastName && !input.appNumber,
118118
)
119119
@IsNotEmpty()

sources/packages/backend/apps/api/src/route-controllers/student/student.aest.controller.ts

+8-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
Body,
33
Controller,
44
Get,
5-
Injectable,
65
NotFoundException,
76
Param,
87
ParseIntPipe,
@@ -40,7 +39,7 @@ import {
4039
AESTFileUploadToStudentAPIInDTO,
4140
AESTStudentFileAPIOutDTO,
4241
AESTStudentProfileAPIOutDTO,
43-
AESTStudentSearchAPIInDTO,
42+
StudentSearchAPIInDTO,
4443
ApplicationSummaryAPIOutDTO,
4544
CreateSINValidationAPIInDTO,
4645
SearchStudentAPIOutDTO,
@@ -57,10 +56,10 @@ import {
5756
MINISTRY_FILE_UPLOAD_GROUP_NAME,
5857
uploadLimits,
5958
} from "../../utilities";
60-
import { CustomNamedError, getISODateOnlyString } from "@sims/utilities";
59+
import { CustomNamedError } from "@sims/utilities";
6160
import { IUserToken } from "../../auth/userToken.interface";
6261
import { StudentControllerService } from "..";
63-
import { FileOriginType, Student } from "@sims/sims-db";
62+
import { FileOriginType } from "@sims/sims-db";
6463
import { FileCreateAPIOutDTO } from "../models/common.dto";
6564
import {
6665
ApplicationPaginationOptionsAPIInDTO,
@@ -81,7 +80,6 @@ import { EntityManager } from "typeorm";
8180
@Groups(UserGroups.AESTUser)
8281
@Controller("student")
8382
@ApiTags(`${ClientTypeBaseRoute.AEST}-student`)
84-
@Injectable()
8583
export class StudentAESTController extends BaseController {
8684
constructor(
8785
private readonly fileService: StudentFileService,
@@ -228,17 +226,12 @@ export class StudentAESTController extends BaseController {
228226
*/
229227
@Post("search")
230228
async searchStudents(
231-
@Body() searchCriteria: AESTStudentSearchAPIInDTO,
229+
@Body() searchCriteria: StudentSearchAPIInDTO,
232230
): Promise<SearchStudentAPIOutDTO[]> {
233-
const searchStudentApplications =
234-
await this.studentService.searchStudentApplication(searchCriteria);
235-
return searchStudentApplications.map((eachStudent: Student) => ({
236-
id: eachStudent.id,
237-
firstName: eachStudent.user.firstName,
238-
lastName: eachStudent.user.lastName,
239-
birthDate: getISODateOnlyString(eachStudent.birthDate),
240-
sin: eachStudent.sinValidation.sin,
241-
}));
231+
const students = await this.studentService.searchStudent(searchCriteria);
232+
return this.studentControllerService.transformStudentsToSearchStudentDetails(
233+
students,
234+
);
242235
}
243236

244237
/**

sources/packages/backend/apps/api/src/route-controllers/student/student.controller.service.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import {
1313
} from "../models/pagination.dto";
1414
import { determinePDStatus, getUserFullName } from "../../utilities";
1515
import { getISODateOnlyString } from "@sims/utilities";
16-
import { AddressInfo, Application } from "@sims/sims-db";
16+
import { AddressInfo, Application, Student } from "@sims/sims-db";
1717
import {
1818
ApplicationSummaryAPIOutDTO,
19+
SearchStudentAPIOutDTO,
1920
StudentProfileAPIOutDTO,
2021
} from "./models/student.dto";
2122
import { transformAddressDetailsForAddressBlockForm } from "../utils/address-utils";
@@ -179,4 +180,21 @@ export class StudentControllerService {
179180
status: application.applicationStatus,
180181
};
181182
};
183+
184+
/**
185+
* Transforms a list of students into a list of student search details.
186+
* @param students list of students.
187+
* @returns a list of student search details.
188+
*/
189+
transformStudentsToSearchStudentDetails(
190+
students: Student[],
191+
): SearchStudentAPIOutDTO[] {
192+
return students.map((eachStudent: Student) => ({
193+
id: eachStudent.id,
194+
firstName: eachStudent.user.firstName,
195+
lastName: eachStudent.user.lastName,
196+
birthDate: getISODateOnlyString(eachStudent.birthDate),
197+
sin: eachStudent.sinValidation.sin,
198+
}));
199+
}
182200
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Body, Controller, Post } from "@nestjs/common";
2+
import { ApiTags } from "@nestjs/swagger";
3+
import { StudentService } from "../../services";
4+
import { ClientTypeBaseRoute } from "../../types";
5+
import { AuthorizedParties } from "../../auth/authorized-parties.enum";
6+
import { AllowAuthorizedParty, UserToken } from "../../auth/decorators";
7+
import BaseController from "../BaseController";
8+
import {
9+
StudentSearchAPIInDTO,
10+
SearchStudentAPIOutDTO,
11+
} from "./models/student.dto";
12+
import { IInstitutionUserToken } from "../../auth";
13+
import { StudentControllerService } from "./student.controller.service";
14+
15+
/**
16+
* Student controller for institutions.
17+
*/
18+
@AllowAuthorizedParty(AuthorizedParties.institution)
19+
@Controller("student")
20+
@ApiTags(`${ClientTypeBaseRoute.Institution}-student`)
21+
export class StudentInstitutionsController extends BaseController {
22+
constructor(
23+
private readonly studentService: StudentService,
24+
private readonly studentControllerService: StudentControllerService,
25+
) {
26+
super();
27+
}
28+
29+
/**
30+
* Search students based on the search criteria.
31+
* TODO add decorator to restrict to BC Public institutions.
32+
* @param searchCriteria criteria to be used in the search.
33+
* @returns searched student details.
34+
*/
35+
@Post("search")
36+
async searchStudents(
37+
@UserToken() userToken: IInstitutionUserToken,
38+
@Body() searchCriteria: StudentSearchAPIInDTO,
39+
): Promise<SearchStudentAPIOutDTO[]> {
40+
const students = await this.studentService.searchStudent(
41+
searchCriteria,
42+
userToken.authorizations.institutionId,
43+
);
44+
return this.studentControllerService.transformStudentsToSearchStudentDetails(
45+
students,
46+
);
47+
}
48+
}

sources/packages/backend/apps/api/src/services/student/student.service.ts

+52-20
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
DisbursementOveraward,
1414
DisbursementOverawardOriginType,
1515
} from "@sims/sims-db";
16-
import { DataSource, EntityManager } from "typeorm";
16+
import { Brackets, DataSource, EntityManager } from "typeorm";
1717
import { StudentUserToken } from "../../auth/userToken.interface";
1818
import { LoggerService, InjectLogger } from "@sims/utilities/logger";
1919
import { removeWhiteSpaces, transformAddressDetails } from "../../utilities";
@@ -438,17 +438,21 @@ export class StudentService extends RecordDataModelService<Student> {
438438
}
439439

440440
/**
441-
* Search students based on the search criteria.
441+
* Search students based on the search criteria and institution id if provided.
442442
* @param searchCriteria options to search by firstName,
443443
* lastName, appNumber or sin.
444-
* @returns list of students.
444+
* @param institutionId id of the institution that the student applied to.
445+
* @returns list of student details.
445446
*/
446-
async searchStudentApplication(searchCriteria: {
447-
firstName?: string;
448-
lastName?: string;
449-
appNumber?: string;
450-
sin?: string;
451-
}): Promise<Student[]> {
447+
async searchStudent(
448+
searchCriteria: {
449+
firstName?: string;
450+
lastName?: string;
451+
appNumber?: string;
452+
sin?: string;
453+
},
454+
institutionId?: number,
455+
): Promise<Student[]> {
452456
const searchQuery = this.repo
453457
.createQueryBuilder("student")
454458
.select([
@@ -457,16 +461,48 @@ export class StudentService extends RecordDataModelService<Student> {
457461
"user.firstName",
458462
"user.lastName",
459463
"sinValidation.sin",
460-
])
461-
.leftJoin(
464+
]);
465+
if (institutionId) {
466+
searchQuery
467+
.innerJoin(
468+
Application,
469+
"application",
470+
"application.student.id = student.id",
471+
)
472+
.leftJoin("application.location", "pirLocation")
473+
.leftJoin("pirLocation.institution", "pirInstitution")
474+
.leftJoin("application.currentAssessment", "studentAssessment")
475+
.leftJoin("studentAssessment.offering", "offering")
476+
.leftJoin("offering.institutionLocation", "offeringLocation")
477+
.leftJoin("offeringLocation.institution", "offeringInstitution");
478+
} else {
479+
searchQuery.leftJoin(
462480
Application,
463481
"application",
464482
"application.student.id = student.id",
465-
)
483+
);
484+
}
485+
searchQuery
466486
.innerJoin("student.user", "user")
467487
.innerJoin("student.sinValidation", "sinValidation")
468488
.where("user.isActive = true");
469-
489+
if (institutionId) {
490+
searchQuery.andWhere(
491+
new Brackets((qb) => {
492+
qb.where("offeringInstitution.id = :institutionId", {
493+
institutionId,
494+
}).orWhere("pirInstitution.id = :institutionId", { institutionId });
495+
}),
496+
);
497+
}
498+
if (institutionId || searchCriteria.appNumber) {
499+
searchQuery.andWhere(
500+
"application.applicationStatus != :overwrittenStatus",
501+
{
502+
overwrittenStatus: ApplicationStatus.Overwritten,
503+
},
504+
);
505+
}
470506
if (searchCriteria.sin) {
471507
searchQuery.andWhere("sinValidation.sin = :sin", {
472508
sin: removeWhiteSpaces(searchCriteria.sin),
@@ -483,13 +519,9 @@ export class StudentService extends RecordDataModelService<Student> {
483519
});
484520
}
485521
if (searchCriteria.appNumber) {
486-
searchQuery
487-
.andWhere("application.applicationNumber Ilike :appNumber")
488-
.andWhere("application.applicationStatus != :overwrittenStatus")
489-
.setParameters({
490-
appNumber: `%${searchCriteria.appNumber}%`,
491-
overwrittenStatus: ApplicationStatus.Overwritten,
492-
});
522+
searchQuery.andWhere("application.applicationNumber Ilike :appNumber", {
523+
appNumber: `%${searchCriteria.appNumber}%`,
524+
});
493525
}
494526
return searchQuery.getMany();
495527
}

sources/packages/backend/libs/sims-db/src/constant.ts

+1
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ export const TableNames = {
5858
QueueConfigurations: "queue_configurations",
5959
};
6060

61+
export const INSTITUTION_TYPE_BC_PUBLIC = 1;
6162
export const INSTITUTION_TYPE_BC_PRIVATE = 2;

0 commit comments

Comments
 (0)