targo-backend/src/modules/timesheets/services/timesheets-query.service.ts

254 lines
9.9 KiB
TypeScript

import { endOfDayUTC, toHHmm, toUTCDateOnly } from '../utils/timesheet.helpers';
import { Injectable, NotFoundException } from '@nestjs/common';
import { formatDateISO, getWeekEnd, getWeekStart } from 'src/common/utils/date-utils';
import { PrismaService } from 'src/prisma/prisma.service';
import { OvertimeService } from 'src/modules/business-logics/services/overtime.service';
import { TimesheetDto } from '../dtos/overview-timesheet.dto';
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
import { ShiftRow, ExpenseRow } from '../types/timesheet.types';
import { buildPeriod } from '../utils/timesheet.utils';
@Injectable()
export class TimesheetsQueryService {
constructor(
private readonly prisma: PrismaService,
// private readonly overtime: OvertimeService,
) {}
async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
//finds the employee
const employee = await this.prisma.employees.findFirst({
where: {
user: { is: { email } }
},
select: {
id: true,
user_id: true,
},
});
if(!employee) throw new NotFoundException(`no employee with email ${email} found`);
//gets the employee's full name
const user = await this.prisma.users.findFirst({
where: { id: employee.user_id },
select: {
first_name: true,
last_name: true,
}
});
const employee_full_name: string = ( user?.first_name + " " + user?.last_name ) || " ";
//finds the period
const period = await this.prisma.payPeriods.findFirst({
where: {
pay_year: year,
pay_period_no: period_no
},
select: {
period_start: true,
period_end: true
},
});
if(!period) throw new NotFoundException(`Period ${year}-${period_no} not found`);
const from = toUTCDateOnly(period.period_start);
const to = endOfDayUTC(period.period_end);
const raw_shifts = await this.prisma.shifts.findMany({
where: {
timesheet: { is: { employee_id: employee.id } },
date: { gte: from, lte: to },
},
select: {
date: true,
start_time: true,
end_time: true,
comment: true,
is_approved: true,
is_remote: true,
bank_code: { select: { type: true } },
},
orderBy:[ { date:'asc'}, { start_time: 'asc'} ],
});
const raw_expenses = await this.prisma.expenses.findMany({
where: {
timesheet: { is: { employee_id: employee.id } },
date: { gte: from, lte: to },
},
select: {
date: true,
amount: true,
mileage: true,
comment: true,
is_approved: true,
supervisor_comment: true,
bank_code: { select: { type: true } },
},
orderBy: { date: 'asc' },
});
const toNum = (value: any) =>
value && typeof value.toNumber === 'function' ? value.toNumber() :
typeof value === 'number' ? value :
value ? Number(value) : 0;
// data mapping
const shifts: ShiftRow[] = raw_shifts.map(shift => ({
date: shift.date,
start_time: shift.start_time,
end_time: shift.end_time,
comment: shift.comment ?? '',
is_approved: shift.is_approved ?? true,
is_remote: shift.is_remote ?? true,
type: String(shift.bank_code?.type ?? '').toUpperCase(),
}));
const expenses: ExpenseRow[] = raw_expenses.map(expense => ({
type: String(expense.bank_code?.type ?? '').toUpperCase(),
date: expense.date,
amount: toNum(expense.amount),
mileage: toNum(expense.mileage),
comment: expense.comment ?? '',
is_approved: expense.is_approved ?? true,
supervisor_comment: expense.supervisor_comment ?? '',
}));
return buildPeriod(period.period_start, period.period_end, shifts , expenses, employee_full_name);
}
async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetDto> {
//fetch user related to email
const user = await this.prisma.users.findUnique({
where: { email },
select: { id: true },
});
if(!user) throw new NotFoundException(`user with email ${email} not found`);
//fetch employee_id matching the email
const employee = await this.prisma.employees.findFirst({
where: { user_id: user.id },
select: { id: true },
});
if(!employee) throw new NotFoundException(`Employee with email: ${email} not found`);
//sets current week Sunday -> Saturday
const base = new Date();
const offset = new Date(base);
offset.setDate(offset.getDate() + (week_offset * 7));
const start_date_week = getWeekStart(offset, 0);
const end_date_week = getWeekEnd(start_date_week);
const start_day = formatDateISO(start_date_week);
const end_day = formatDateISO(end_date_week);
//build the label MM/DD/YYYY.MM/DD.YYYY
const mm_dd = (date: Date) => `${String(date.getFullYear())}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2,'0')}`;
const label = `${mm_dd(start_date_week)}.${mm_dd(end_date_week)}`;
//fetch timesheet shifts and expenses
const timesheet = await this.prisma.timesheets.findUnique({
where: {
employee_id_start_date: {
employee_id: employee.id,
start_date: start_date_week,
},
},
include: {
shift: {
include: { bank_code: true },
orderBy: [{ date: 'asc'}, { start_time: 'asc'}],
},
expense: {
include: { bank_code: true },
orderBy: [{date: 'asc'}],
},
},
});
//returns an empty timesheet if not found
if(!timesheet) {
return {
is_approved: false,
start_day,
end_day,
label,
shifts:[],
expenses: [],
} as TimesheetDto;
}
//small helper to format hours:minutes
//maps all shifts of selected timesheet
const shifts = timesheet.shift.map((shift_row) => ({
bank_type: shift_row.bank_code?.type ?? '',
date: formatDateISO(shift_row.date),
start_time: toHHmm(shift_row.start_time),
end_time: toHHmm(shift_row.end_time),
comment: shift_row.comment ?? '',
is_approved: shift_row.is_approved ?? false,
is_remote: shift_row.is_remote ?? false,
}));
//maps all expenses of selected timsheet
const expenses = timesheet.expense.map((exp) => ({
bank_type: exp.bank_code?.type ?? '',
date: formatDateISO(exp.date),
amount: Number(exp.amount) || 0,
mileage: exp.mileage != null ? Number(exp.mileage) : 0,
comment: exp.comment ?? '',
is_approved: exp.is_approved ?? false,
supervisor_comment: exp.supervisor_comment ?? '',
}));
return {
is_approved: timesheet.is_approved,
start_day,
end_day,
label,
shifts,
expenses,
} as TimesheetDto;
}
//_____________________________________________________________________________________________
// Deprecated or unused methods
//_____________________________________________________________________________________________
// async findOne(id: number): Promise<any> {
// const timesheet = await this.prisma.timesheets.findUnique({
// where: { id },
// include: {
// shift: { include: { bank_code: true } },
// expense: { include: { bank_code: true } },
// employee: { include: { user: true } },
// },
// });
// if(!timesheet) {
// throw new NotFoundException(`Timesheet #${id} not found`);
// }
// const detailedShifts = timesheet.shift.map( s => {
// const hours = computeHours(s.start_time, s.end_time);
// const regularHours = Math.min(8, hours);
// const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time);
// const payRegular = regularHours * s.bank_code.modifier;
// const payOvertime = this.overtime.calculateOvertimePay(dailyOvertime, s.bank_code.modifier);
// return { ...s, hours, payRegular, payOvertime };
// });
// const weeklyOvertimeHours = detailedShifts.length
// ? await this.overtime.getWeeklyOvertimeHours(
// timesheet.employee_id,
// timesheet.shift[0].date): 0;
// return { ...timesheet, shift: detailedShifts, weeklyOvertimeHours };
// }
// async remove(id: number): Promise<Timesheets> {
// await this.findOne(id);
// return this.prisma.timesheets.delete({ where: { id } });
// }
}