import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; import { NotificationsService } from "src/modules/notifications/services/notifications.service"; import { computeHours } from "src/common/utils/date-utils"; import { OverviewRow } from "../types and interfaces/shifts-overview-row.interface"; // const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 12); @Injectable() export class ShiftsQueryService { constructor( private readonly prisma: PrismaService, private readonly notifs: NotificationsService, ) {} async getSummary(period_id: number): Promise { //fetch pay-period to display const period = await this.prisma.payPeriods.findFirst({ where: { pay_period_no: period_id }, }); if(!period) { throw new NotFoundException(`pay-period ${period_id} not found`); } const { period_start, period_end } = period; //prepare shifts and expenses for display const shifts = await this.prisma.shifts.findMany({ where: { date: { gte: period_start, lte: period_end } }, include: { bank_code: true, timesheet: { include: { employee: { include: { user:true, supervisor: { include: { user: true } }, } }, } }, }, }); const expenses = await this.prisma.expenses.findMany({ where: { date: { gte: period_start, lte: period_end } }, include: { bank_code: true, timesheet: { include: { employee: { include: { user:true, supervisor: { include: { user:true } }, } }, } }, }, }); const mapRow = new Map(); for(const shift of shifts) { const employeeId = shift.timesheet.employee.user_id; const user = shift.timesheet.employee.user; const sup = shift.timesheet.employee.supervisor?.user; let row = mapRow.get(employeeId); if(!row) { row = { full_name: `${user.first_name} ${user.last_name}`, supervisor: sup? `${sup.first_name} ${sup.last_name }` : '', total_regular_hrs: 0, total_evening_hrs: 0, total_overtime_hrs: 0, total_expenses: 0, total_mileage: 0, is_approved: false, }; } const hours = computeHours(shift.start_time, shift.end_time); switch(shift.bank_code.type) { case 'regular' : row.total_regular_hrs += hours; break; case 'evening' : row.total_evening_hrs += hours; break; case 'overtime' : row.total_overtime_hrs += hours; break; default: row.total_regular_hrs += hours; } mapRow.set(employeeId, row); } for(const exp of expenses) { const employee_id = exp.timesheet.employee.user_id; const user = exp.timesheet.employee.user; const sup = exp.timesheet.employee.supervisor?.user; let row = mapRow.get(employee_id); if(!row) { row = { full_name: `${user.first_name} ${user.last_name}`, supervisor: sup? `${sup.first_name} ${sup.last_name }` : '', total_regular_hrs: 0, total_evening_hrs: 0, total_overtime_hrs: 0, total_expenses: 0, total_mileage: 0, is_approved: false, }; } const amount = Number(exp.amount); row.total_expenses += amount; if(exp.bank_code.type === 'mileage') { row.total_mileage += amount; } mapRow.set(employee_id, row); } //return by default the list of employee in ascending alphabetical order return Array.from(mapRow.values()).sort((a,b) => a.full_name.localeCompare(b.full_name)); } //_____________________________________________________________________________________________ // Deprecated or unused methods //_____________________________________________________________________________________________ // async update(id: number, dto: UpdateShiftsDto): Promise { // await this.findOne(id); // const { timesheet_id, bank_code_id, date,start_time,end_time, comment} = dto; // return this.prisma.shifts.update({ // where: { id }, // data: { // ...(timesheet_id !== undefined && { timesheet_id }), // ...(bank_code_id !== undefined && { bank_code_id }), // ...(date !== undefined && { date }), // ...(start_time !== undefined && { start_time }), // ...(end_time !== undefined && { end_time }), // ...(comment !== undefined && { comment }), // }, // include: { timesheet: { include: { employee: { include: { user: true } } } }, // bank_code: true, // }, // }); // } // async remove(id: number): Promise { // await this.findOne(id); // return this.prisma.shifts.delete({ where: { id } }); // } // async create(dto: CreateShiftDto): Promise { // const { timesheet_id, bank_code_id, date, start_time, end_time, comment } = dto; // //shift creation // const shift = await this.prisma.shifts.create({ // data: { timesheet_id, bank_code_id, date, start_time, end_time, comment }, // include: { timesheet: { include: { employee: { include: { user: true } } } }, // bank_code: true, // }, // }); // //fetches all shifts of the same day to check for daily overtime // const same_day_shifts = await this.prisma.shifts.findMany({ // where: { timesheet_id, date }, // select: { id: true, date: true, start_time: true, end_time: true }, // }); // //sums hours of the day // const total_hours = same_day_shifts.reduce((sum, s) => { // return sum + hoursBetweenSameDay(s.date, s.start_time, s.end_time); // }, 0 ); // //Notify if total hours > 8 for a single day // if(total_hours > DAILY_LIMIT_HOURS ) { // const user_id = String(shift.timesheet.employee.user.id); // const date_label = new Date(date).toLocaleDateString('fr-CA'); // this.notifs.notify(user_id, { // type: 'shift.overtime.daily', // severity: 'warn', // message: `Tu viens de dépasser ${DAILY_LIMIT_HOURS.toFixed(2)}h pour la journée du ${date_label} // (total: ${total_hours.toFixed(2)}h).`, // ts: new Date().toISOString(), // meta: { // timesheet_id, // date: new Date(date).toISOString(), // total_hours, // threshold: DAILY_LIMIT_HOURS, // last_shift_id: shift.id // }, // }); // } // return shift; // } // async findAll(filters: SearchShiftsDto): Promise { // const where = buildPrismaWhere(filters); // const shifts = await this.prisma.shifts.findMany({ where }) // return shifts; // } // async findOne(id: number): Promise { // const shift = await this.prisma.shifts.findUnique({ // where: { id }, // include: { timesheet: { include: { employee: { include: { user: true } } } }, // bank_code: true, // }, // }); // if(!shift) { // throw new NotFoundException(`Shift #${id} not found`); // } // return shift; // } }