refactor(shared): centralized some small logics

This commit is contained in:
Matthieu Haineault 2025-10-14 16:43:18 -04:00
parent cefba7a2dd
commit 06ad34a4c8
9 changed files with 79 additions and 52 deletions

View File

@ -5,7 +5,7 @@ import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-employee-timesheet.utils";
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
import {
BadRequestException,
Injectable,
@ -66,7 +66,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
const employee_id = await this.emailResolver.findIdByEmail(email);
//make sure a timesheet existes
const timesheet_id = await this.timesheetsResolver.ensureForDate(employee_id, date_only);
const timesheet_id = await this.timesheetsResolver.findTimesheetIdByEmail(email, date_only);
if(!timesheet_id) throw new NotFoundException(`no timesheet found for employee #${employee_id}`)
const {id} = timesheet_id;

View File

@ -0,0 +1,9 @@
export interface ShiftKey {
timesheet_id: number;
date: Date;
start_time: Date;
end_time: Date;
bank_code_id: number;
is_remote: boolean;
comment?: string | null;
}

View File

@ -1,6 +1,6 @@
import { Module } from "@nestjs/common";
import { EmailToIdResolver } from "./utils/resolve-email-id.utils";
import { EmployeeTimesheetResolver } from "./utils/resolve-employee-timesheet.utils";
import { EmployeeTimesheetResolver } from "./utils/resolve-timesheet.utils";
import { FullNameResolver } from "./utils/resolve-full-name.utils";
import { BankCodesResolver } from "./utils/resolve-bank-type-id.utils";
import { PrismaModule } from "src/prisma/prisma.module";

View File

@ -1,42 +0,0 @@
import { Injectable } from "@nestjs/common";
import { Prisma, PrismaClient } from "@prisma/client";
import { weekStartSunday } from "src/modules/shifts/helpers/shifts-date-time-helpers";
import { PrismaService } from "src/prisma/prisma.service";
type Tx = Prisma.TransactionClient | PrismaClient;
@Injectable()
export class EmployeeTimesheetResolver {
constructor(private readonly prisma: PrismaService) {}
//find an existing timesheet linked to the employee
readonly ensureForDate = async (employee_id: number, date: Date, client?: Tx,
): Promise<{id: number; start_date: Date }> => {
const db = client ?? this.prisma;
const startOfWeek = weekStartSunday(date);
const existing = await db.timesheets.findFirst({
where: {
employee_id: employee_id,
start_date: startOfWeek,
},
select: {
id: true,
start_date: true,
},
});
if(existing) return existing;
const created = await db.timesheets.create({
data: {
employee_id: employee_id,
start_date: startOfWeek,
},
select: {
id: true,
start_date: true,
},
});
return created;
}
}

View File

@ -0,0 +1,29 @@
import { Prisma, PrismaClient } from "@prisma/client";
import { NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { ShiftKey } from "../interfaces/shifts.interface";
type Tx = Prisma.TransactionClient | PrismaClient;
export class ShiftIdResolver {
constructor(private readonly prisma: PrismaService) {}
readonly findShiftIdByData = async ( key: ShiftKey, client?: Tx ): Promise<{id:number}> => {
const db = client ?? this.prisma;
const shift = await db.shifts.findFirst({
where: {
timesheet_id: key.timesheet_id,
bank_code_id: key.bank_code_id,
date: key.date,
start_time: key.start_time,
end_time: key.end_time,
is_remote: key.is_remote,
comment: key.comment,
},
select: { id: true },
});
if(!shift) throw new NotFoundException(`shift not found`);
return { id: shift.id };
};
}

View File

@ -0,0 +1,28 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, PrismaClient } from "@prisma/client";
import { weekStartSunday } from "src/modules/shifts/helpers/shifts-date-time-helpers";
import { PrismaService } from "src/prisma/prisma.service";
import { EmailToIdResolver } from "./resolve-email-id.utils";
type Tx = Prisma.TransactionClient | PrismaClient;
@Injectable()
export class EmployeeTimesheetResolver {
constructor(
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
) {}
readonly findTimesheetIdByEmail = async (email: string, date: Date, client?: Tx): Promise<{id: number}> => {
const db = client ?? this.prisma;
const employee_id = await this.emailResolver.findIdByEmail(email);
const start_date = weekStartSunday(date);
const timesheet = await db.timesheets.findFirst({
where: { employee_id : employee_id, start_date: start_date },
select: { id: true },
});
if(!timesheet) throw new NotFoundException(`timesheet not found`);
return { id: timesheet.id };
}
}

View File

@ -11,7 +11,7 @@ export function toDateOnly(ymd: string): Date {
}
export function weekStartSunday(date_local: Date): Date {
const start = new Date(date_local.getFullYear(), date_local.getMonth(), date_local.getDate());
const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate()));
const dow = start.getDay(); // 0 = dimanche
start.setDate(start.getDate() - dow);
start.setHours(0, 0, 0, 0);

View File

@ -114,10 +114,13 @@ export class ShiftsHelpersService {
async afterWriteOvertimeAndLog(tx: Tx, employee_id: number, date_only: Date) {
// Switch regular → weekly overtime si > 40h
await this.overtimeService.transformRegularHoursToWeeklyOvertime(employee_id, date_only, tx);
const [daily, weekly] = await Promise.all([
this.overtimeService.getDailyOvertimeHoursForDay(employee_id, date_only),
this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only),
]);
const daily = await this.overtimeService.getDailyOvertimeHoursForDay(employee_id, date_only);
const weekly = await this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only);
// const [daily, weekly] = await Promise.all([
// this.overtimeService.getDailyOvertimeHoursForDay(employee_id, date_only),
// this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only),
// ]);
return { daily, weekly };
}
async mapDay(

View File

@ -1,5 +1,5 @@
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-employee-timesheet.utils";
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
import { parseISODate, parseHHmm } from "../utils-helpers-others/timesheet.helpers";
import { TimesheetsQueryService } from "./timesheets-query.service";
@ -69,7 +69,7 @@ export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
const start_week = getWeekStart(base, 0);
const end_week = getWeekEnd(start_week);
const timesheet = await this.timesheetResolver.ensureForDate(employee_id, base)
const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, base)
if(!timesheet) throw new NotFoundException(`no timesheet found for employe ${employee_id}`);
//validations and insertions