diff --git a/backend/app/src/attendance/attendance.service.ts b/backend/app/src/attendance/attendance.service.ts index 4cba8c4..d1a53db 100644 --- a/backend/app/src/attendance/attendance.service.ts +++ b/backend/app/src/attendance/attendance.service.ts @@ -2,12 +2,23 @@ import { Service } from '@libs/decorator' import { EntityNotExistException, UnexpectedException } from '@libs/exception' import { PrismaService } from '@libs/prisma' import { calculatePaginationOffset } from '@libs/utils' -import { Prisma } from '@prisma/client' +import { + Prisma, + RosterType, + type Attendance, + type Roster +} from '@prisma/client' import type { AttendanceWithRoster, CreateAttendanceDTO } from './dto/attendance.dto' +export type AttendanceCount = { + present: number + absence: number + tardy: number +} + @Service() export class AttendanceService { constructor(private readonly prisma: PrismaService) {} @@ -90,4 +101,147 @@ export class AttendanceService { throw new UnexpectedException(error) } } + + async getAttendancesGroupedByPosition(scheduleId: number) { + try { + const athleteAttendances = await this.prisma.attendance.findMany({ + where: { + scheduleId, + Roster: { + registerYear: { + not: new Date().getFullYear() + }, + type: RosterType.Athlete + } + }, + include: { + Roster: true + } + }) + + const athleteNewbieAttendances = await this.prisma.attendance.findMany({ + where: { + scheduleId, + Roster: { + registerYear: new Date().getFullYear(), + type: RosterType.Athlete + } + }, + include: { + Roster: true + } + }) + + const staffAttendances = await this.prisma.attendance.findMany({ + where: { + scheduleId, + Roster: { + type: RosterType.Staff + } + }, + include: { + Roster: true + } + }) + + return { + athlete: this.calculateAthleteAttendances(athleteAttendances), + athleteNewbie: this.calculateAthleteAttendances( + athleteNewbieAttendances + ), + staff: this.calculateStaffAttendances(staffAttendances) + } + } catch (error) { + throw new UnexpectedException(error) + } + } + + private calculateStaffAttendances( + // eslint-disable-next-line @typescript-eslint/naming-convention + attendancesWithRosters: (Attendance & { Roster: Roster })[] + ): Record> { + const positionCounts = { + staff: { + normal: { + present: 0, + absence: 0, + tardy: 0 + }, + newbie: { + present: 0, + absence: 0, + tardy: 0 + } + } + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + attendancesWithRosters.forEach(({ Roster, response }) => { + const isNewbie = Roster.registerYear === new Date().getFullYear() + const counts = positionCounts['staff'][isNewbie ? 'newbie' : 'normal'] + + switch (response) { + case 'Present': + counts.present++ + break + case 'Absence': + counts.absence++ + break + case 'Tardy': + counts.tardy++ + break + } + }) + + return positionCounts + } + + private calculateAthleteAttendances( + // eslint-disable-next-line @typescript-eslint/naming-convention + attendancesWithRosters: (Attendance & { Roster: Roster })[] + ): Record> { + const positionCounts = { + off: {}, + def: {}, + spl: {} + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + attendancesWithRosters.forEach(({ Roster, response }) => { + const updateCounts = ( + positionType: string, + positionValue: string | null + ) => { + if (positionValue) { + if (!positionCounts[positionType][positionValue]) { + positionCounts[positionType][positionValue] = { + present: 0, + absence: 0, + tardy: 0 + } + } + + const counts = positionCounts[positionType][positionValue] + + switch (response) { + case 'Present': + counts.present++ + break + case 'Absence': + counts.absence++ + break + case 'Tardy': + counts.tardy++ + break + } + } + } + + updateCounts('off', Roster.offPosition) + updateCounts('def', Roster.defPosition) + updateCounts('spl', Roster.splPosition) + }) + + return positionCounts + } } diff --git a/backend/app/src/survey/survey.controller.ts b/backend/app/src/survey/survey.controller.ts index 990f138..ec15ada 100644 --- a/backend/app/src/survey/survey.controller.ts +++ b/backend/app/src/survey/survey.controller.ts @@ -51,6 +51,18 @@ export class SurveyController { } } + @Roles(Role.Manager) + @Get('/groups/:surveyGroupId/unsubmits') + async getUnsubmitList( + @Param('surveyGroupId', ParseIntPipe) surveyGroupId: number + ) { + try { + return await this.surveyService.getUnsubmitList(surveyGroupId) + } catch (error) { + BusinessExceptionHandler(error) + } + } + @Public() @Get('/groups') async getSurveyGroups( diff --git a/backend/app/src/survey/survey.service.ts b/backend/app/src/survey/survey.service.ts index 85e69a1..7f6024b 100644 --- a/backend/app/src/survey/survey.service.ts +++ b/backend/app/src/survey/survey.service.ts @@ -258,6 +258,44 @@ export class SurveyService { } } + async getUnsubmitList(surveyGroupId: number) { + try { + const surveyTargets = await this.prisma.surveyTarget.findMany({ + where: { + surveyGroupId, + submit: false + }, + select: { + Roster: { + select: { + name: true, + admissionYear: true, + profileImageUrl: true + } + } + } + }) + + const unsubmitList = surveyTargets.map((item) => { + return { + name: item.Roster.name, + admissionYear: item.Roster.admissionYear, + profileImageUrl: item.Roster.profileImageUrl + } + }) + + return { rosters: unsubmitList } + } catch (error) { + if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === 'P2003' + ) { + throw new EntityNotExistException('출석조사가 존재하지 않습니다') + } + throw new UnexpectedException(error) + } + } + private async createSurveyTargets(surveyGroupId: number): Promise { try { const surveyTargetRosters = await this.prisma.roster.findMany({