diff --git a/src/entities/pendaftaranSidsem.ts b/src/entities/pendaftaranSidsem.ts index d4e391a4251aaefc582f27d338a451fb7b9ad2b4..901bcd467d91ca787f2cbb58f0cb70f7ad19291b 100644 --- a/src/entities/pendaftaranSidsem.ts +++ b/src/entities/pendaftaranSidsem.ts @@ -10,6 +10,8 @@ import { PendaftaranTesis } from "./pendaftaranTesis.entity"; import { ApiProperty } from "@nestjs/swagger"; import { PengujiSidsem } from "./pengujiSidsem.entity"; import { BerkasSidsem } from "./berkasSidsem.entity"; +import { IsEnum, IsUUID } from "@nestjs/class-validator"; +import { IsDateString, IsNotEmpty, IsString } from "class-validator"; export enum TipeSidsemEnum { SEMINAR_1 = "SEMINAR_1", @@ -26,14 +28,17 @@ export enum SidsemStatus { @Entity() export class PendaftaranSidsem { @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) + @IsUUID() @PrimaryGeneratedColumn("uuid") id: string; @ApiProperty({ enum: TipeSidsemEnum }) + @IsEnum(TipeSidsemEnum) @Column({ type: "enum", enum: TipeSidsemEnum }) tipe: TipeSidsemEnum; @ApiProperty() + @IsEnum(SidsemStatus) @Column({ type: "enum", enum: SidsemStatus, @@ -42,14 +47,19 @@ export class PendaftaranSidsem { status: SidsemStatus; @ApiProperty() + @IsDateString() @Column({ type: "timestamptz", nullable: true }) jadwal: Date; @ApiProperty() + @IsString() + @IsNotEmpty() @Column({ type: "text" }) judulSidsem: string; @ApiProperty() + @IsString() + @IsNotEmpty() @Column({ type: "text" }) deskripsiSidsem: string; @@ -57,6 +67,8 @@ export class PendaftaranSidsem { pendaftaranTesis: PendaftaranTesis; @ApiProperty() + @IsString() + @IsNotEmpty() @Column({ type: "text", nullable: true }) ruangan: string; @@ -77,3 +89,13 @@ export class PendaftaranSidsem { @Column({ type: "timestamptz", default: () => "CURRENT_TIMESTAMP" }) waktuPengiriman: Date; } + +export function cmpTipeSidsem(a: TipeSidsemEnum, b: TipeSidsemEnum): number { + const tipeSidsemOrder = { + [TipeSidsemEnum.SEMINAR_1]: 1, + [TipeSidsemEnum.SEMINAR_2]: 2, + [TipeSidsemEnum.SIDANG]: 3, + }; + + return tipeSidsemOrder[a] - tipeSidsemOrder[b]; +} diff --git a/src/entities/pengujiSidsem.entity.ts b/src/entities/pengujiSidsem.entity.ts index 09aebe7c16acbb068ae8a26c341b02e996e6a7a3..934f0da7dbfc9520ee87e55c12492db4575a203b 100644 --- a/src/entities/pengujiSidsem.entity.ts +++ b/src/entities/pengujiSidsem.entity.ts @@ -1,4 +1,10 @@ -import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, +} from "typeorm"; import { Pengguna } from "./pengguna.entity"; import { PendaftaranSidsem } from "./pendaftaranSidsem"; @@ -11,8 +17,16 @@ export class PengujiSidsem { () => PendaftaranSidsem, (pendaftaranSidsem) => pendaftaranSidsem.id, ) + @JoinColumn({ name: "idSidsem" }) sidsem: PendaftaranSidsem; + @Column() + idSidsem: string; + @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) + @JoinColumn({ name: "idDosen" }) dosen: Pengguna; + + @Column() + idDosen: string; } diff --git a/src/registrasi-sidsem/registrasi-sidsem.controller.ts b/src/registrasi-sidsem/registrasi-sidsem.controller.ts index 86a3fafc50b19ad185eafdf0d25cf3b6a2116e89..2891d5a5e5d92bb43835500a05789ef98e0b0ac2 100644 --- a/src/registrasi-sidsem/registrasi-sidsem.controller.ts +++ b/src/registrasi-sidsem/registrasi-sidsem.controller.ts @@ -4,58 +4,92 @@ import { Get, Param, Patch, + Post, Query, + Req, UseGuards, } from "@nestjs/common"; import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, + ApiOperation, ApiTags, } from "@nestjs/swagger"; import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; import { Roles } from "src/middlewares/roles.decorator"; import { RolesGuard } from "src/middlewares/roles.guard"; import { + CreatePengajuanSidsemDto, GetAllPengajuanSidangReqQueryDto, GetAllPengajuanSidangRespDto, - GetOnePengajuanSidangRespDto, - UpdateAlokasiRuanganReqDto, - UpdateAlokasiRuanganRespDto, + PengajuanSidsemIdDto, + SidsemMhsIdParamDto, + UpdateSidsemDetailDto, + UpdateSidsemStatusDto, } from "./registrasi-sidsem.dto"; import { RegistrasiSidsemService } from "./registrasi-sidsem.service"; +import { Request } from "express"; +import { AuthDto } from "src/auth/auth.dto"; +import { RoleEnum } from "src/entities/pengguna.entity"; import { HIGH_AUTHORITY_ROLES } from "src/helper/roles"; -@ApiTags("Alokasi Ruangan") +@ApiTags("Registrasi Sidang Seminar") @ApiBearerAuth() @ApiCookieAuth() @UseGuards(CustomAuthGuard, RolesGuard) -@Roles(...HIGH_AUTHORITY_ROLES) -@Controller("alokasi-ruangan") +@Controller("registrasi-sidsem") export class RegistrasiSidsemController { constructor(private readonly regisSidsemService: RegistrasiSidsemService) {} + + @ApiOkResponse({ type: PengajuanSidsemIdDto }) + @Roles(RoleEnum.S2_MAHASISWA) + @Post() + async create(@Req() req: Request, @Body() dto: CreatePengajuanSidsemDto) { + const { id } = req.user as AuthDto; + return this.regisSidsemService.create(id, dto); + } + @ApiOkResponse({ type: GetAllPengajuanSidangRespDto }) @Get() - async findAll( - @Query() query: GetAllPengajuanSidangReqQueryDto, - ): Promise<GetAllPengajuanSidangRespDto> { + async findAll(@Query() query: GetAllPengajuanSidangReqQueryDto) { return this.regisSidsemService.findAll(query); } - @ApiOkResponse({ type: GetOnePengajuanSidangRespDto }) - @Get(":id") - async findOne( - @Param("id") id: string, - ): Promise<GetOnePengajuanSidangRespDto> { - return this.regisSidsemService.findOne(id); + @ApiOperation({ + summary: "Update status sidang seminar. Roles: ADMIN, S2_TIM_TESIS", + }) + @ApiOkResponse({ type: PengajuanSidsemIdDto }) + // @Roles(...HIGH_AUTHORITY_ROLES) + @Get("/mahasiswa/:mhsId") + async findOne(@Param() param: SidsemMhsIdParamDto) { + return this.regisSidsemService.findOne(param.mhsId); + } + + @ApiOperation({ + summary: "Update status sidang seminar. Roles: ADMIN, S2_TIM_TESIS", + }) + @ApiOkResponse({ type: PengajuanSidsemIdDto }) + @Roles(...HIGH_AUTHORITY_ROLES) + @Patch("/mahasiswa/:mhsId/status") + async updateStatus( + @Param() param: SidsemMhsIdParamDto, + @Body() updateDto: UpdateSidsemStatusDto, + ) { + return this.regisSidsemService.updateStatus(param.mhsId, updateDto.status); } - @ApiOkResponse({ type: UpdateAlokasiRuanganRespDto }) - @Patch(":id") - async update( - @Param("id") id: string, - @Body() updateAlokasiRuanganDto: UpdateAlokasiRuanganReqDto, - ): Promise<UpdateAlokasiRuanganRespDto> { - return this.regisSidsemService.update(id, updateAlokasiRuanganDto); + @ApiOperation({ + summary: + "Update detail of approved sidang seminar. Any falsify valued field will be ignored. Roles: ADMIN, S2_TIM_TESIS", + }) + @ApiOkResponse({ type: PengajuanSidsemIdDto }) + @Roles(...HIGH_AUTHORITY_ROLES) + @Patch("/mahasiswa/:mhsId/detail") + async updateDetail( + @Param() param: SidsemMhsIdParamDto, + @Body() updateDto: UpdateSidsemDetailDto, + ) { + return this.regisSidsemService.updateDetail(param.mhsId, updateDto); } } diff --git a/src/registrasi-sidsem/registrasi-sidsem.dto.ts b/src/registrasi-sidsem/registrasi-sidsem.dto.ts index 94d819b7fb4360abc1532d74a0acb874d7a05d50..6c8cbc8e9855c5c283db2af2b09bde080da84c37 100644 --- a/src/registrasi-sidsem/registrasi-sidsem.dto.ts +++ b/src/registrasi-sidsem/registrasi-sidsem.dto.ts @@ -1,6 +1,26 @@ -import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { IsEnum, IsNumberString, IsOptional, IsString } from "class-validator"; -import { TipeSidsemEnum } from "src/entities/pendaftaranSidsem"; +import { + ApiProperty, + ApiPropertyOptional, + OmitType, + PickType, +} from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { + ArrayNotEmpty, + IsDateString, + IsEnum, + IsNumberString, + IsOptional, + IsString, + IsUUID, + ValidateNested, +} from "class-validator"; +import { BerkasSidsem } from "src/entities/berkasSidsem.entity"; +import { + PendaftaranSidsem, + SidsemStatus, + TipeSidsemEnum, +} from "src/entities/pendaftaranSidsem"; import { JalurEnum } from "src/entities/pendaftaranTesis.entity"; export class GetAllPengajuanSidangReqQueryDto { @@ -29,6 +49,9 @@ export class GetAllPengajuanSidangItemDto { @ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000" }) idPengajuanSidsem: string; + @ApiProperty() + idMahasiswa: string; + @ApiProperty() nimMahasiswa: string; @@ -43,6 +66,15 @@ export class GetAllPengajuanSidangItemDto { @ApiProperty({ nullable: true }) ruangan: string | null; + + @ApiProperty({ enum: SidsemStatus }) + status: SidsemStatus; + + @ApiProperty({ type: [String] }) + dosenPembimbing: string[]; + + @ApiProperty({ type: [BerkasSidsem] }) + berkasSidsem: BerkasSidsem[]; } export class GetAllPengajuanSidangRespDto { @@ -68,12 +100,51 @@ export class GetOnePengajuanSidangRespDto extends GetAllPengajuanSidangItemDto { dosenPenguji: string[]; } -export class UpdateAlokasiRuanganReqDto { +export class UpdateAlokasiRuanganRespDto extends GetAllPengajuanSidangItemDto {} + +class BerkasSidsemWithoutId extends OmitType(BerkasSidsem, ["id"] as const) {} + +export class CreatePengajuanSidsemDto extends PickType(PendaftaranSidsem, [ + "judulSidsem", + "deskripsiSidsem", + "tipe", +]) { + @ApiProperty({ type: [BerkasSidsemWithoutId] }) + @ValidateNested({ each: true }) + @ArrayNotEmpty() + @Type(() => BerkasSidsemWithoutId) + berkasSidsem: BerkasSidsemWithoutId[]; +} + +export class PengajuanSidsemIdDto extends PickType(PendaftaranSidsem, [ + "id", +] as const) {} + +export class UpdateSidsemDetailDto { + @ApiPropertyOptional() + @IsOptional() @IsString() - @ApiProperty({ - description: "Can send empty string, will update DB to set as null", - }) - ruangan: string; + ruangan?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsDateString() + jadwal?: Date; + + @ApiPropertyOptional() + @IsOptional() + @IsUUID("all", { each: true }) + dosenPengujiIds?: string[]; } -export class UpdateAlokasiRuanganRespDto extends GetAllPengajuanSidangItemDto {} +export class SidsemMhsIdParamDto { + @IsUUID() + @ApiProperty() + mhsId: string; +} + +export class UpdateSidsemStatusDto { + @IsEnum([SidsemStatus.APPROVED, SidsemStatus.REJECTED]) + @ApiProperty({ enum: [SidsemStatus.APPROVED, SidsemStatus.REJECTED] }) + status: SidsemStatus.APPROVED | SidsemStatus.REJECTED; +} diff --git a/src/registrasi-sidsem/registrasi-sidsem.module.ts b/src/registrasi-sidsem/registrasi-sidsem.module.ts index 93cf75168059b29e898d20244b5ce15764eb8870..37af696b48ea52963d04e037d01f18d89037cc0d 100644 --- a/src/registrasi-sidsem/registrasi-sidsem.module.ts +++ b/src/registrasi-sidsem/registrasi-sidsem.module.ts @@ -5,23 +5,32 @@ import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem"; import { PendaftaranTesis } from "src/entities/pendaftaranTesis.entity"; import { Pengguna } from "src/entities/pengguna.entity"; -import { PengujiSidsem } from "src/entities/pengujiSidsem.entity"; import { CustomStrategy } from "src/middlewares/custom.strategy"; import { RegistrasiSidsemController } from "./registrasi-sidsem.controller"; import { RegistrasiSidsemService } from "./registrasi-sidsem.service"; +import { RegistrasiTesisService } from "src/registrasi-tesis/registrasi-tesis.service"; +import { RegistrasiTesisModule } from "src/registrasi-tesis/registrasi-tesis.module"; +import { BerkasSidsem } from "src/entities/berkasSidsem.entity"; +import { Topik } from "src/entities/topik.entity"; +import { PenggunaModule } from "src/pengguna/pengguna.module"; +import { PengujiSidsem } from "src/entities/pengujiSidsem.entity"; @Module({ imports: [ TypeOrmModule.forFeature([ PendaftaranSidsem, - PengujiSidsem, DosenBimbingan, PendaftaranTesis, Pengguna, + BerkasSidsem, + Topik, + PengujiSidsem, ]), AuthModule, + RegistrasiTesisModule, + PenggunaModule, ], controllers: [RegistrasiSidsemController], - providers: [RegistrasiSidsemService, CustomStrategy], + providers: [RegistrasiSidsemService, CustomStrategy, RegistrasiTesisService], }) export class RegistrasiSidsemModule {} diff --git a/src/registrasi-sidsem/registrasi-sidsem.service.ts b/src/registrasi-sidsem/registrasi-sidsem.service.ts index c54803a6122f38dc912ac0dd6c5476802cbec987..7daa85328b03ef5a6f1285eab47943671749fd34 100644 --- a/src/registrasi-sidsem/registrasi-sidsem.service.ts +++ b/src/registrasi-sidsem/registrasi-sidsem.service.ts @@ -1,17 +1,31 @@ -import { BadRequestException, Injectable } from "@nestjs/common"; +import { + BadRequestException, + Injectable, + InternalServerErrorException, + NotFoundException, +} from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; -import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; -import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem"; +import { + cmpTipeSidsem, + PendaftaranSidsem, + SidsemStatus, + TipeSidsemEnum, +} from "src/entities/pendaftaranSidsem"; import { PengujiSidsem } from "src/entities/pengujiSidsem.entity"; -import { Brackets, Repository } from "typeorm"; +import { DataSource, In, Repository, Brackets } from "typeorm"; import { + CreatePengajuanSidsemDto, GetAllPengajuanSidangItemDto, GetAllPengajuanSidangReqQueryDto, GetAllPengajuanSidangRespDto, GetOnePengajuanSidangRespDto, - UpdateAlokasiRuanganReqDto, - UpdateAlokasiRuanganRespDto, + PengajuanSidsemIdDto, + UpdateSidsemDetailDto, } from "./registrasi-sidsem.dto"; +import { RegStatus } from "src/entities/pendaftaranTesis.entity"; +import { RegistrasiTesisService } from "src/registrasi-tesis/registrasi-tesis.service"; +import { BerkasSidsem } from "src/entities/berkasSidsem.entity"; +import { Pengguna, RoleEnum } from "src/entities/pengguna.entity"; @Injectable() export class RegistrasiSidsemService { @@ -20,33 +34,159 @@ export class RegistrasiSidsemService { private pendaftaranSidsemRepo: Repository<PendaftaranSidsem>, @InjectRepository(PengujiSidsem) private pengujiSidsemRepo: Repository<PengujiSidsem>, - @InjectRepository(DosenBimbingan) - private dosenBimbinganRepo: Repository<DosenBimbingan>, + @InjectRepository(Pengguna) + private penggunaRepo: Repository<Pengguna>, + @InjectRepository(BerkasSidsem) + private berkasSidsemRepo: Repository<BerkasSidsem>, + private regTesisService: RegistrasiTesisService, + private dataSource: DataSource, ) {} + private async getLatestPendaftaranSidsem(mhsId: string) { + return await this.pendaftaranSidsemRepo + .createQueryBuilder("ps") + .select([ + "ps.id", + "ps.tipe", + "ps.jadwal", + "ps.ruangan", + "ps.status", + "ps.judulSidsem", + "ps.deskripsiSidsem", + "berkasSidsem", + "pt.id", + "pt.jalurPilihan", + "mahasiswa.id", + "mahasiswa.nim", + "mahasiswa.nama", + "mahasiswa.email", + "topik.judul", + "topik.deskripsi", + "dosenBimbingan.id", + "dosen.id", + "dosen.nama", + "penguji.id", + "dosenPenguji.id", + "dosenPenguji.nama", + ]) + .leftJoin("ps.penguji", "penguji") + .leftJoin("ps.berkasSidsem", "berkasSidsem") + .leftJoin("penguji.dosen", "dosenPenguji") + .leftJoin("ps.pendaftaranTesis", "pt") + .leftJoin("pt.mahasiswa", "mahasiswa") + .leftJoin("pt.topik", "topik") + .leftJoin("pt.dosenBimbingan", "dosenBimbingan") + .leftJoin("dosenBimbingan.dosen", "dosen") + .where("pt.mahasiswaId = :mhsId", { mhsId }) + .andWhere("mahasiswa.aktif = true") + .orderBy("ps.waktuPengiriman", "DESC") + .getOne(); + } + + async create( + mhsId: string, + dto: CreatePengajuanSidsemDto, + ): Promise<PengajuanSidsemIdDto> { + const regTesis = await this.regTesisService.getNewestRegByMhsOrFail(mhsId); + + if (regTesis.status !== RegStatus.APPROVED) { + throw new BadRequestException( + "Mahasiswa belum diterima sebagai mahasiswa tesis.", + ); + } + + // Check if mahasiswa already has pending registration + const lastPendaftaran = await this.getLatestPendaftaranSidsem(mhsId); + if (lastPendaftaran) { + const delta = cmpTipeSidsem(dto.tipe, lastPendaftaran.tipe); + + if ( + (delta !== 0 && delta !== 1) || + (delta === 0 && lastPendaftaran.status !== SidsemStatus.REJECTED) || + (delta === 1 && lastPendaftaran.status !== SidsemStatus.APPROVED) + ) { + { + throw new BadRequestException("Tipe sidsem invalid"); + } + } + } else { + if (dto.tipe !== TipeSidsemEnum.SEMINAR_1) { + throw new BadRequestException("Tipe sidsem invalid"); + } + } + + const berkasSidsem = dto.berkasSidsem.map((berkasSubmisiTugas) => + this.berkasSidsemRepo.create(berkasSubmisiTugas), + ); + + // Create new registration + const createdRegistration = this.pendaftaranSidsemRepo.create({ + ...dto, + pendaftaranTesis: regTesis, + berkasSidsem, + }); + + await this.pendaftaranSidsemRepo.save(createdRegistration); + + return { + id: createdRegistration.id, + }; + } + async findAll( query: GetAllPengajuanSidangReqQueryDto, + idPembimbing?: string, + idPenguji?: string, ): Promise<GetAllPengajuanSidangRespDto> { const baseQuery = this.pendaftaranSidsemRepo .createQueryBuilder("ps") + .select([ + "ps.id", + "ps.tipe", + "ps.status", + "pt.id", + "mahasiswa.id", + "mahasiswa.nim", + "mahasiswa.nama", + "dosenBimbingan.id", + "dosen.id", + "dosen.nama", + "berkasSidsem", + ]) .innerJoinAndSelect( (qb) => qb .select([ "ps.pendaftaranTesisId AS latest_pendaftaranTesisId", - "ps.tipe AS latest_tipe", "MAX(ps.waktuPengiriman) AS latestPengiriman", ]) .from(PendaftaranSidsem, "ps") - .groupBy("ps.pendaftaranTesisId") - .addGroupBy("ps.tipe"), + .groupBy("ps.pendaftaranTesisId"), "latest", - "latest.latest_pendaftaranTesisId = ps.pendaftaranTesisId AND latest.latest_tipe = ps.tipe AND ps.waktuPengiriman = latest.latestPengiriman", + "latest.latest_pendaftaranTesisId = ps.pendaftaranTesisId AND ps.waktuPengiriman = latest.latestPengiriman", ) - .innerJoinAndSelect("ps.pendaftaranTesis", "pt") - .innerJoinAndSelect("pt.mahasiswa", "mahasiswa") + .innerJoin("ps.pendaftaranTesis", "pt") + .innerJoin("ps.berkasSidsem", "berkasSidsem") + .innerJoin("pt.dosenBimbingan", "dosenBimbingan") + .innerJoin("dosenBimbingan.dosen", "dosen") + .innerJoin("pt.mahasiswa", "mahasiswa") + .where("mahasiswa.aktif = true") .orderBy("ps.waktuPengiriman", "DESC"); + if (idPembimbing) { + baseQuery.andWhere("dosenBimbingan.dosenId = :idPembimbing", { + idPembimbing, + }); + } + + if (idPenguji) { + baseQuery + .innerJoin("ps.penguji", "penguji") + .andWhere("penguji.idDosen = :idPenguji", { + idPenguji, + }); + } + if (query.search) { baseQuery.andWhere( new Brackets((qb) => @@ -76,146 +216,151 @@ export class RegistrasiSidsemService { const data: GetAllPengajuanSidangItemDto[] = queryData.map((res) => ({ idPengajuanSidsem: res.id, + idMahasiswa: res.pendaftaranTesis.mahasiswa.id, nimMahasiswa: res.pendaftaranTesis.mahasiswa.nim, namaMahasiswa: res.pendaftaranTesis.mahasiswa.nama, jadwalSidang: !!res.jadwal ? res.jadwal.toISOString() : null, jenisSidang: res.tipe, ruangan: res.ruangan, + status: res.status, + dosenPembimbing: res.pendaftaranTesis.dosenBimbingan.map( + (dosen) => dosen.dosen.nama, + ), + berkasSidsem: res.berkasSidsem, })); return { data, total }; } - async findOne(id: string): Promise<GetOnePengajuanSidangRespDto> { - const sidsemQueryData = await this.pendaftaranSidsemRepo.findOne({ - select: { - id: true, - jadwal: true, - tipe: true, - ruangan: true, - pendaftaranTesis: { - id: true, - mahasiswa: { - nim: true, - nama: true, - email: true, - }, - jalurPilihan: true, - topik: { - judul: true, - deskripsi: true, - }, - }, - }, - relations: { - pendaftaranTesis: { - mahasiswa: true, - topik: true, - }, - }, - where: { - id, - }, - }); - - if (sidsemQueryData === null) - throw new BadRequestException( - "Pendaftaran sidang with given id does not exist", - ); - - const pengujiQueryData = await this.pengujiSidsemRepo.find({ - select: { - dosen: { - nama: true, - }, - }, - relations: { - dosen: true, - }, - where: { - sidsem: { - id, - }, - }, - }); + async findOne(mhsId: string): Promise<GetOnePengajuanSidangRespDto> { + const latest = await this.getLatestPendaftaranSidsem(mhsId); - const pembimbingQueryData = await this.dosenBimbinganRepo.find({ - select: { - dosen: { - nama: true, - }, - }, - relations: { - dosen: true, - }, - where: { - idPendaftaran: sidsemQueryData.pendaftaranTesis.id, - }, - }); + if (!latest) { + throw new NotFoundException("Pendaftaran sidsem tidak ditemukan"); + } const data: GetOnePengajuanSidangRespDto = { - idPengajuanSidsem: sidsemQueryData.id, - nimMahasiswa: sidsemQueryData.pendaftaranTesis.mahasiswa.nim, - namaMahasiswa: sidsemQueryData.pendaftaranTesis.mahasiswa.nama, - jadwalSidang: sidsemQueryData.jadwal.toISOString(), - jenisSidang: sidsemQueryData.tipe, - ruangan: sidsemQueryData.ruangan, - emailMahasiswa: sidsemQueryData.pendaftaranTesis.mahasiswa.email, - jalurPilihan: sidsemQueryData.pendaftaranTesis.jalurPilihan, - judulTopik: sidsemQueryData.pendaftaranTesis.topik.judul, - deskripsiTopik: sidsemQueryData.pendaftaranTesis.topik.deskripsi, - dosenPembimbing: pembimbingQueryData.map(({ dosen: { nama } }) => nama), - dosenPenguji: pengujiQueryData.map(({ dosen: { nama } }) => nama), + idPengajuanSidsem: latest.id, + idMahasiswa: latest.pendaftaranTesis.mahasiswa.id, + nimMahasiswa: latest.pendaftaranTesis.mahasiswa.nim, + namaMahasiswa: latest.pendaftaranTesis.mahasiswa.nama, + emailMahasiswa: latest.pendaftaranTesis.mahasiswa.email, + jadwalSidang: latest.jadwal ? latest.jadwal.toISOString() : null, + jenisSidang: latest.tipe, + ruangan: latest.ruangan, + jalurPilihan: latest.pendaftaranTesis.jalurPilihan, + judulTopik: latest.pendaftaranTesis.topik.judul, + deskripsiTopik: latest.pendaftaranTesis.topik.deskripsi, + status: latest.status, + berkasSidsem: latest.berkasSidsem, + dosenPembimbing: latest.pendaftaranTesis.dosenBimbingan.map( + ({ dosen: { nama } }) => nama, + ), + dosenPenguji: latest.penguji.map(({ dosen: { nama } }) => nama), }; return data; } - async update( - id: string, - updateAlokasiRuanganDto: UpdateAlokasiRuanganReqDto, - ): Promise<UpdateAlokasiRuanganRespDto> { - const pendaftaranSidsem = await this.pendaftaranSidsemRepo.findOne({ - select: { - id: true, - jadwal: true, - tipe: true, - pendaftaranTesis: { - id: true, - mahasiswa: { - nama: true, - nim: true, - }, - }, - }, - relations: { - pendaftaranTesis: { - mahasiswa: true, - }, - }, - where: { - id, - }, + async updateStatus( + mhsId: string, + status: SidsemStatus.REJECTED | SidsemStatus.APPROVED, + ): Promise<PengajuanSidsemIdDto> { + const latest = await this.getLatestPendaftaranSidsem(mhsId); + + if (!latest || latest.status !== SidsemStatus.NOT_ASSIGNED) { + throw new BadRequestException( + "Pendaftaran sidsem yang pending tidak ditemukan", + ); + } + + await this.pendaftaranSidsemRepo.update(latest.id, { + status, }); - if (pendaftaranSidsem === null) + return { id: latest.id } as PengajuanSidsemIdDto; + } + + async updateDetail( + mhsId: string, + updateDto: UpdateSidsemDetailDto, + ): Promise<PengajuanSidsemIdDto> { + const latest = await this.getLatestPendaftaranSidsem(mhsId); + + if (!latest || latest.status !== SidsemStatus.APPROVED) { throw new BadRequestException( - "Pendaftaran sidang/seminar with diven id does not exist", + "Pendaftaran sidsem yang disetujui tidak ditemukan", ); + } + + if (updateDto.dosenPengujiIds) { + const newPengujiList = await this.penggunaRepo.findBy({ + id: In(updateDto.dosenPengujiIds), + }); - pendaftaranSidsem.ruangan = - updateAlokasiRuanganDto.ruangan === "" - ? null - : updateAlokasiRuanganDto.ruangan; - await this.pendaftaranSidsemRepo.save(pendaftaranSidsem); + if ( + newPengujiList.length !== updateDto.dosenPengujiIds.length || + newPengujiList.some( + (dosen) => !dosen.roles.includes(RoleEnum.S2_PENGUJI), + ) + ) + throw new BadRequestException( + "Dosen id list contains invalid user ids", + ); - return { - idPengajuanSidsem: pendaftaranSidsem.id, - jadwalSidang: pendaftaranSidsem.jadwal.toISOString(), - jenisSidang: pendaftaranSidsem.tipe, - namaMahasiswa: pendaftaranSidsem.pendaftaranTesis.mahasiswa.nama, - nimMahasiswa: pendaftaranSidsem.pendaftaranTesis.mahasiswa.nim, - ruangan: pendaftaranSidsem.ruangan, - }; + const currentPenguji = await this.pengujiSidsemRepo.findBy({ + idSidsem: latest.id, + }); + + const newPengujiIds = newPengujiList.map((dosen) => dosen.id); + const currentPengujiIds = currentPenguji.map( + (currentPembimbing) => currentPembimbing.idDosen, + ); + + const idsToBeAdded = newPengujiIds.filter( + (newId) => !currentPengujiIds.includes(newId), + ); + + const idsToBeDeleted = currentPengujiIds.filter( + (newId) => !newPengujiIds.includes(newId), + ); + + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + await queryRunner.manager.insert( + PengujiSidsem, + idsToBeAdded.map((idDosen) => ({ sidsem: latest, idDosen })), + ); + await queryRunner.manager.delete(PengujiSidsem, { + idDosen: In(idsToBeDeleted), + }); + + if (updateDto.ruangan || updateDto.jadwal) { + await queryRunner.manager.update(PendaftaranSidsem, latest.id, { + ruangan: updateDto.ruangan, + jadwal: updateDto.jadwal, + }); + } + + await queryRunner.commitTransaction(); + } catch (err) { + await queryRunner.rollbackTransaction(); + + console.error(err); + + throw new InternalServerErrorException(); + } finally { + await queryRunner.release(); + } + } else { + await this.pendaftaranSidsemRepo.update(latest.id, { + ...updateDto, + }); + } + + return { id: latest.id } as PengajuanSidsemIdDto; } } diff --git a/src/registrasi-tesis/registrasi-tesis.module.ts b/src/registrasi-tesis/registrasi-tesis.module.ts index 0b64d44cec70b955ad05a008eedd82f63760e1c3..907d8d686e7472c7218f0608ae1c9eb4a74e8781 100644 --- a/src/registrasi-tesis/registrasi-tesis.module.ts +++ b/src/registrasi-tesis/registrasi-tesis.module.ts @@ -1,28 +1,29 @@ -import { Module } from "@nestjs/common"; -import { PendaftaranTesis } from "src/entities/pendaftaranTesis.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { RegistrasiTesisController } from "./registrasi-tesis.controller"; -import { RegistrasiTesisService } from "./registrasi-tesis.service"; -import { Topik } from "src/entities/topik.entity"; -import { CustomStrategy } from "src/middlewares/custom.strategy"; -import { AuthModule } from "src/auth/auth.module"; -import { PenggunaModule } from "src/pengguna/pengguna.module"; -import { PenggunaService } from "src/pengguna/pengguna.service"; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ - Pengguna, - DosenBimbingan, - PendaftaranTesis, - Topik, - ]), - AuthModule, - PenggunaModule, - ], - controllers: [RegistrasiTesisController], - providers: [RegistrasiTesisService, CustomStrategy, PenggunaService], -}) -export class RegistrasiTesisModule {} +import { Module } from "@nestjs/common"; +import { PendaftaranTesis } from "src/entities/pendaftaranTesis.entity"; +import { Pengguna } from "src/entities/pengguna.entity"; +import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { RegistrasiTesisController } from "./registrasi-tesis.controller"; +import { RegistrasiTesisService } from "./registrasi-tesis.service"; +import { Topik } from "src/entities/topik.entity"; +import { CustomStrategy } from "src/middlewares/custom.strategy"; +import { AuthModule } from "src/auth/auth.module"; +import { PenggunaModule } from "src/pengguna/pengguna.module"; +import { PenggunaService } from "src/pengguna/pengguna.service"; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Pengguna, + DosenBimbingan, + PendaftaranTesis, + Topik, + ]), + AuthModule, + PenggunaModule, + ], + controllers: [RegistrasiTesisController], + providers: [RegistrasiTesisService, CustomStrategy, PenggunaService], + exports: [RegistrasiTesisService], +}) +export class RegistrasiTesisModule {} diff --git a/src/registrasi-tesis/registrasi-tesis.service.ts b/src/registrasi-tesis/registrasi-tesis.service.ts index 17e59c6096fcf7f65aff48a21dda444938e8515e..4efe8d5d5be8bc2b1bb94cab81c1da1381d473aa 100644 --- a/src/registrasi-tesis/registrasi-tesis.service.ts +++ b/src/registrasi-tesis/registrasi-tesis.service.ts @@ -373,7 +373,7 @@ export class RegistrasiTesisService { return resData; } - private async getNewestRegByMhsOrFail(mahasiswaId: string) { + async getNewestRegByMhsOrFail(mahasiswaId: string) { const mahasiswa = await this.penggunaRepository.findOne({ select: { id: true, @@ -381,11 +381,14 @@ export class RegistrasiTesisService { }, where: { id: mahasiswaId, + aktif: true, }, }); if (!mahasiswa || !mahasiswa.roles.includes(RoleEnum.S2_MAHASISWA)) - throw new BadRequestException("No mahasiswa user with given id exists"); + throw new BadRequestException( + "No active mahasiswa user with given id exists", + ); const newestReg = await this.pendaftaranTesisRepository.findOne({ select: {