diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index cca65b6..8aa7528 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -91,30 +91,20 @@ "parameters": [ { "name": "date", - "required": false, + "required": true, "in": "query", - "description": "Override for resolving the current period", "schema": { - "example": "2025-08-11", "type": "string" } } ], "responses": { "200": { - "description": "Find current and all pay periods", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayPeriodBundleDto" - } - } - } + "description": "" } }, - "summary": "Return current pay period and the full list", "tags": [ - "pay-periods" + "PayPeriods" ] } }, @@ -133,22 +123,11 @@ ], "responses": { "200": { - "description": "Pay period found for the selected date", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayPeriodDto" - } - } - } - }, - "404": { - "description": "Pay period not found for the selected date" + "description": "" } }, - "summary": "Resolve a period by a date within it", "tags": [ - "pay-periods" + "PayPeriods" ] } }, @@ -161,7 +140,6 @@ "required": true, "in": "path", "schema": { - "example": 2024, "type": "number" } }, @@ -169,31 +147,18 @@ "name": "periodNumber", "required": true, "in": "path", - "description": "1..26", "schema": { - "example": 1, "type": "number" } } ], "responses": { "200": { - "description": "Pay period found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayPeriodDto" - } - } - } - }, - "404": { - "description": "Pay period not found" + "description": "" } }, - "summary": "Find pay period by year and period number", "tags": [ - "pay-periods" + "PayPeriods" ] } }, @@ -206,7 +171,6 @@ "required": true, "in": "path", "schema": { - "example": 2024, "type": "number" } }, @@ -214,49 +178,18 @@ "name": "periodNumber", "required": true, "in": "path", - "description": "1..26", "schema": { - "example": 1, "type": "number" } - }, - { - "name": "email", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "includeSubtree", - "required": false, - "in": "query", - "description": "Include indirect reports", - "schema": { - "example": false, - "type": "boolean" - } } ], "responses": { "200": { - "description": "Crew overview", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayPeriodOverviewDto" - } - } - } - }, - "404": { - "description": "Pay period not found" + "description": "" } }, - "summary": "Supervisor crew overview for a given pay period", "tags": [ - "pay-periods" + "PayPeriods" ] } }, @@ -269,7 +202,6 @@ "required": true, "in": "path", "schema": { - "example": 2024, "type": "number" } }, @@ -277,31 +209,18 @@ "name": "periodNumber", "required": true, "in": "path", - "description": "1..26", "schema": { - "example": 1, "type": "number" } } ], "responses": { "200": { - "description": "Pay period overview found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PayPeriodOverviewDto" - } - } - } - }, - "404": { - "description": "Pay period not found" + "description": "" } }, - "summary": "Detailed view of a pay period by year + number", "tags": [ - "pay-periods" + "PayPeriods" ] } }, @@ -690,168 +609,6 @@ } }, "schemas": { - "PayPeriodDto": { - "type": "object", - "properties": { - "pay_period_no": { - "type": "number", - "example": 1, - "description": "numéro cyclique de la période entre 1 et 26" - }, - "period_start": { - "type": "string", - "example": "2023-12-17", - "format": "date" - }, - "period_end": { - "type": "string", - "example": "2023-12-30", - "format": "date" - }, - "payday": { - "type": "string", - "example": "2023-01-04", - "format": "date" - }, - "pay_year": { - "type": "number", - "example": 2023 - }, - "label": { - "type": "string", - "example": "2023-12-17 → 2023-12-30" - } - }, - "required": [ - "pay_period_no", - "period_start", - "period_end", - "payday", - "pay_year", - "label" - ] - }, - "PayPeriodBundleDto": { - "type": "object", - "properties": { - "current": { - "description": "Current pay period (resolved from date)", - "allOf": [ - { - "$ref": "#/components/schemas/PayPeriodDto" - } - ] - }, - "periods": { - "description": "All pay periods", - "type": "array", - "items": { - "$ref": "#/components/schemas/PayPeriodDto" - } - } - }, - "required": [ - "current", - "periods" - ] - }, - "EmployeePeriodOverviewDto": { - "type": "object", - "properties": { - "employee_name": { - "type": "string", - "example": "Alex Dupont", - "description": "Nom complet de lemployé" - }, - "regular_hours": { - "type": "number", - "example": 40, - "description": "pay-period`s regular hours" - }, - "other_hours": { - "type": "object", - "example": 0, - "description": "pay-period`s other hours" - }, - "expenses": { - "type": "number", - "example": 420.69, - "description": "pay-period`s total expenses ($)" - }, - "mileage": { - "type": "number", - "example": 40, - "description": "pay-period total mileages (km)" - }, - "is_approved": { - "type": "boolean", - "example": true, - "description": "Tous les timesheets de la période sont approuvés pour cet employé" - } - }, - "required": [ - "employee_name", - "regular_hours", - "other_hours", - "expenses", - "mileage", - "is_approved" - ] - }, - "PayPeriodOverviewDto": { - "type": "object", - "properties": { - "pay_period_no": { - "type": "number", - "example": 1, - "description": "Period number (1–26)" - }, - "pay_year": { - "type": "number", - "example": 2023, - "description": "Calendar year of the period" - }, - "period_start": { - "type": "string", - "example": "2023-12-17", - "format": "date", - "description": "Period start date (YYYY-MM-DD)" - }, - "period_end": { - "type": "string", - "example": "2023-12-30", - "format": "date", - "description": "Period end date (YYYY-MM-DD)" - }, - "payday": { - "type": "string", - "example": "2023-12-30", - "format": "date", - "description": "Period pay day(YYYY-MM-DD)" - }, - "label": { - "type": "string", - "example": "2023-12-17 → 2023-12-30", - "description": "Human-readable label" - }, - "employees_overview": { - "description": "Per-employee overview for the period", - "type": "array", - "items": { - "$ref": "#/components/schemas/EmployeePeriodOverviewDto" - } - } - }, - "required": [ - "pay_period_no", - "pay_year", - "period_start", - "period_end", - "payday", - "label", - "employees_overview" - ] - }, "PreferencesDto": { "type": "object", "properties": {} diff --git a/src/modules/exports/csv-exports.module.ts b/src/modules/exports/csv-exports.module.ts index 30a9b8d..92a5a96 100644 --- a/src/modules/exports/csv-exports.module.ts +++ b/src/modules/exports/csv-exports.module.ts @@ -1,10 +1,9 @@ import { Module } from "@nestjs/common"; import { CsvExportController } from "./controllers/csv-exports.controller"; import { CsvExportService } from "./services/csv-exports.service"; -import { SharedModule } from "src/time-and-attendance/shared/shared.module"; @Module({ - providers:[CsvExportService, SharedModule], + providers:[CsvExportService], controllers: [CsvExportController], }) export class CsvExportModule {} diff --git a/src/time-and-attendance/domains/business-logics.module.ts b/src/time-and-attendance/domains/business-logics.module.ts index 91774ec..4dc801e 100644 --- a/src/time-and-attendance/domains/business-logics.module.ts +++ b/src/time-and-attendance/domains/business-logics.module.ts @@ -4,15 +4,18 @@ import { VacationService } from "./services/vacation.service"; import { HolidayService } from "./services/holiday.service"; import { MileageService } from "./services/mileage.service"; import { Module } from "@nestjs/common"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @Module({ + imports:[], providers: [ HolidayService, MileageService, OvertimeService, SickLeaveService, - VacationService + VacationService, + EmailToIdResolver, ], exports: [ HolidayService, diff --git a/src/time-and-attendance/domains/services/holiday.service.ts b/src/time-and-attendance/domains/services/holiday.service.ts index 15bf4d2..5a0c2b7 100644 --- a/src/time-and-attendance/domains/services/holiday.service.ts +++ b/src/time-and-attendance/domains/services/holiday.service.ts @@ -1,9 +1,8 @@ import { Injectable, Logger, NotFoundException } from "@nestjs/common"; import { computeHours, getWeekStart } from "src/common/utils/date-utils"; -import { PrismaService } from "../../../prisma/prisma.service"; - -const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; - +import { PrismaService } from "src/prisma/prisma.service"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; +import { MS_PER_WEEK } from "src/time-and-attendance/utils/constants.utils"; /* le calcul est 1/20 des 4 dernières semaines, précédent la semaine incluant le férier. Un maximum de 08h00 est allouable pour le férier @@ -15,28 +14,19 @@ const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; export class HolidayService { private readonly logger = new Logger(HolidayService.name); - constructor(private readonly prisma: PrismaService) {} - - //fetch employee_id by email - private async resolveEmployeeByEmail(email: string): Promise { - const employee = await this.prisma.employees.findFirst({ - where: { - user: { email } - }, - select: { id: true }, - }); - if(!employee) throw new NotFoundException(`Employee with email : ${email} not found`); - return employee.id; - } + constructor( + private readonly prisma: PrismaService, + private readonly emailResolver: EmailToIdResolver, + ) {} private async computeHoursPrevious4WeeksByEmail(email: string, holiday_date: Date): Promise { - const employee_id = await this.resolveEmployeeByEmail(email); + const employee_id = await this.emailResolver.findIdByEmail(email); return this.computeHoursPrevious4Weeks(employee_id, holiday_date); } private async computeHoursPrevious4Weeks(employee_id: number, holiday_date: Date): Promise { const holiday_week_start = getWeekStart(holiday_date); - const window_start = new Date(holiday_week_start.getTime() - 4 * WEEK_IN_MS); + const window_start = new Date(holiday_week_start.getTime() - 4 * MS_PER_WEEK); const window_end = new Date(holiday_week_start.getTime() - 1); const valid_codes = ['G1', 'G43', 'G56', 'G104', 'G105', 'G700']; @@ -60,7 +50,7 @@ export class HolidayService { let capped_total = 0; for(let offset = 1; offset <= 4; offset++) { - const week_start = new Date(holiday_week_start.getTime() - offset * WEEK_IN_MS); + const week_start = new Date(holiday_week_start.getTime() - offset * MS_PER_WEEK); const key = week_start.getTime(); const weekly_hours = hours_by_week.get(key) ?? 0; capped_total += Math.min(weekly_hours, 40); diff --git a/src/time-and-attendance/domains/services/mileage.service.ts b/src/time-and-attendance/domains/services/mileage.service.ts index 030ce42..2b589a9 100644 --- a/src/time-and-attendance/domains/services/mileage.service.ts +++ b/src/time-and-attendance/domains/services/mileage.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common"; -import { PrismaService } from '../../../prisma/prisma.service'; +import { PrismaService } from "src/prisma/prisma.service"; @Injectable() export class MileageService { diff --git a/src/time-and-attendance/domains/services/overtime.service.ts b/src/time-and-attendance/domains/services/overtime.service.ts index be8c15c..e43c4d3 100644 --- a/src/time-and-attendance/domains/services/overtime.service.ts +++ b/src/time-and-attendance/domains/services/overtime.service.ts @@ -1,32 +1,15 @@ import { Injectable, Logger } from '@nestjs/common'; -import { PrismaService } from '../../../prisma/prisma.service'; import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils'; -import { Prisma, PrismaClient } from '@prisma/client'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { DAILY_LIMIT_HOURS, WEEKLY_LIMIT_HOURS } from 'src/time-and-attendance/utils/constants.utils'; +import { Tx, WeekOvertimeSummary } from 'src/time-and-attendance/utils/type.utils'; -type Tx = Prisma.TransactionClient | PrismaClient; - -export type WeekOvertimeSummary = { - week_start:string; - week_end: string; - week_total_hours: number; - weekly_overtime: number; - daily_overtime_kept: number; - total_overtime: number; - breakdown: Array<{ - date:string; - day_hours: number; - day_overtime: number; - daily_kept: number; - running_total_before: number; - }>; -}; @Injectable() export class OvertimeService { private logger = new Logger(OvertimeService.name); - private daily_max = 8; // maximum for regular hours per day - private weekly_max = 40; // maximum for regular hours per week + private INCLUDED_TYPES = ['EMERGENCY', 'EVENING','OVERTIME','REGULAR'] as const; // included types for weekly overtime calculation constructor(private prisma: PrismaService) {} @@ -61,7 +44,7 @@ export class OvertimeService { } const week_total_hours = [ ...day_totals.values()].reduce((a,b) => a + b, 0); - const weekly_overtime = Math.max(0, week_total_hours - this.weekly_max); + const weekly_overtime = Math.max(0, week_total_hours - WEEKLY_LIMIT_HOURS); let running = 0; let daily_kept_sum = 0; @@ -69,9 +52,9 @@ export class OvertimeService { for (const key of days) { const day_hours = day_totals.get(key) ?? 0; - const day_overtime = Math.max(0, day_hours - this.daily_max); + const day_overtime = Math.max(0, day_hours - DAILY_LIMIT_HOURS); - const cap_before_40 = Math.max(0, this.weekly_max - running); + const cap_before_40 = Math.max(0, WEEKLY_LIMIT_HOURS - running); const daily_kept = Math.min(day_overtime, cap_before_40); breakdown.push({ @@ -104,144 +87,4 @@ export class OvertimeService { breakdown, }; } - - // //calculate daily overtime - // async getDailyOvertimeHours(timesheet_id: number, date: Date): Promise { - // const shifts = await this.prisma.shifts.findMany({ - // where: { - // timesheet_id, - // date: date, - // bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } }, - // }, - // select: { start_time: true, end_time: true }, - // orderBy: [{ start_time: 'asc' }], - // }); - - // const total = shifts.map((shift)=> - // computeHours(shift.start_time, shift.end_time, 5)). - // reduce((sum, hours)=> sum + hours, 0); - - // const overtime = Math.max(0, total - this.daily_max); - - // this.logger.debug(`[OVERTIME]-[DAILY] total=${total.toFixed(2)}h, overtime= ${overtime.toFixed(2)}h`); - // return overtime; - // } - - // //calculate Weekly overtime - // async getWeeklyOvertimeHours(timesheet_id: number, ref_date: Date): Promise { - // const week_start = getWeekStart(ref_date); - // const week_end = getWeekEnd(week_start); - - // //fetches all shifts from INCLUDED_TYPES array - // const included_shifts = await this.prisma.shifts.findMany({ - // where: { - // timesheet_id, - // date: { gte:week_start, lte: week_end }, - // bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } }, - // }, - // select: { start_time: true, end_time: true }, - // orderBy: [{date: 'asc'}, {start_time:'asc'}], - // }); - - // //calculate total hours of those shifts minus weekly Max to find total overtime hours - // const total = included_shifts.map(shift => - // computeHours(shift.start_time, shift.end_time, 5)). - // reduce((sum, hours)=> sum+hours, 0); - - // const overtime = Math.max(0, total - this.weekly_max); - - // this.logger.debug(`[OVERTIME]-[WEEKLY] total=${total.toFixed(2)}h, overtime= ${overtime.toFixed(2)}h`); - // return overtime; - // } - - - // //transform REGULAR shifts to OVERTIME when exceed 40hrs of included_types of shift - // async transformRegularHoursToWeeklyOvertime( - // employee_id: number, - // ref_date: Date, - // tx?: Prisma.TransactionClient, - // ): Promise { - // //ensures the use of the transaction if needed. fallback to this.prisma if no transaction is detected. - // const db = tx ?? this.prisma; - - // //calculate weekly overtime - // const overtime_hours = await this.getWeeklyOvertimeHours(employee_id, ref_date); - // if(overtime_hours <= 0) return; - - // const convert_to_minutes = Math.round(overtime_hours * 60); - - // const [regular, overtime] = await Promise.all([ - // db.bankCodes.findFirst({where: { type: 'REGULAR' }, select: { id: true } }), - // db.bankCodes.findFirst({where: { type: 'OVERTIME'}, select: { id: true } }), - // ]); - // if(!regular || !overtime) return; - - // const week_start = getWeekStart(ref_date); - // const week_end = getWeekEnd(week_start); - - // //gets all regular shifts and order them by desc - // const regular_shifts_desc = await db.shifts.findMany({ - // where: { - // date: { gte:week_start, lte: week_end }, - // timesheet: { employee_id }, - // bank_code_id: regular.id, - // }, - // select: { - // id: true, - // timesheet_id: true, - // date: true, - // start_time: true, - // end_time: true, - // is_remote: true, - // comment: true, - // }, - // orderBy: [{date: 'desc'}, {start_time:'desc'}], - // }); - - // let remaining_minutes = convert_to_minutes; - - // for(const shift of regular_shifts_desc) { - // if(remaining_minutes <= 0) break; - - // const start = shift.start_time; - // const end = shift.end_time; - // const duration_in_minutes = Math.max(0, Math.round((end.getTime() - start.getTime())/60000)); - // if(duration_in_minutes === 0) continue; - - // if(duration_in_minutes <= remaining_minutes) { - // await db.shifts.update({ - // where: { id: shift.id }, - // data: { bank_code_id: overtime.id }, - // }); - // remaining_minutes -= duration_in_minutes; - // continue; - // } - // //sets the start_time of the new overtime shift - // const new_overtime_start = new Date(end.getTime() - remaining_minutes * 60000); - - // //shorten the regular shift - // await db.shifts.update({ - // where: { id: shift.id }, - // data: { end_time: new_overtime_start }, - // }); - - // //creates the new overtime shift to replace the shorten regular shift - // await db.shifts.create({ - // data: { - // timesheet_id: shift.timesheet_id, - // date: shift.date, - // start_time: new_overtime_start, - // end_time: end, - // is_remote: shift.is_remote, - // comment: shift.comment, - // bank_code_id: overtime.id, - // }, - // }); - // remaining_minutes = 0; - // } - // this.logger.debug(`[OVERTIME]-[WEEKLY]-[TRANSFORM] emp=${employee_id} - // week: ${week_start.toISOString().slice(0,10)}..${week_end.toISOString().slice(0,10)} - // converted= ${(convert_to_minutes-remaining_minutes)/60}h`); - // } - } diff --git a/src/time-and-attendance/domains/services/sick-leave.service.ts b/src/time-and-attendance/domains/services/sick-leave.service.ts index 6c00113..60a847e 100644 --- a/src/time-and-attendance/domains/services/sick-leave.service.ts +++ b/src/time-and-attendance/domains/services/sick-leave.service.ts @@ -1,6 +1,6 @@ import { getYearStart, roundToQuarterHour } from "src/common/utils/date-utils"; import { Injectable, Logger } from "@nestjs/common"; -import { PrismaService } from "../../../prisma/prisma.service"; +import { PrismaService } from "src/prisma/prisma.service"; @Injectable() export class SickLeaveService { diff --git a/src/time-and-attendance/domains/services/vacation.service.ts b/src/time-and-attendance/domains/services/vacation.service.ts index 9445149..2e3a214 100644 --- a/src/time-and-attendance/domains/services/vacation.service.ts +++ b/src/time-and-attendance/domains/services/vacation.service.ts @@ -1,5 +1,5 @@ import { Injectable, Logger, NotFoundException } from "@nestjs/common"; -import { PrismaService } from "../../../prisma/prisma.service"; +import { PrismaService } from "src/prisma/prisma.service"; @Injectable() export class VacationService { diff --git a/src/time-and-attendance/expenses/services/expense-upsert.service.ts b/src/time-and-attendance/expenses/services/expense-upsert.service.ts index 86a1c9b..d48ea50 100644 --- a/src/time-and-attendance/expenses/services/expense-upsert.service.ts +++ b/src/time-and-attendance/expenses/services/expense-upsert.service.ts @@ -6,7 +6,7 @@ import { expense_select } from "src/time-and-attendance/utils/selects.utils"; import { PrismaService } from "src/prisma/prisma.service"; import { ExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-create.dto"; import { GetExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-get.dto"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @Injectable() diff --git a/src/time-and-attendance/leave-requests/leave-requests.module.ts b/src/time-and-attendance/leave-requests/leave-requests.module.ts index a822a27..eff4f6d 100644 --- a/src/time-and-attendance/leave-requests/leave-requests.module.ts +++ b/src/time-and-attendance/leave-requests/leave-requests.module.ts @@ -1,15 +1,13 @@ import { LeaveRequestController } from "src/time-and-attendance/leave-requests/controllers/leave-requests.controller"; import { LeaveRequestsService } from "src/time-and-attendance/leave-requests/services/leave-request.service"; import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module"; -import { SharedModule } from "src/time-and-attendance/shared/shared.module"; import { ShiftsModule } from "src/time-and-attendance/time-tracker/shifts/shifts.module"; import { Module } from "@nestjs/common"; @Module({ imports: [ BusinessLogicsModule, - ShiftsModule, - SharedModule + ShiftsModule, ], controllers: [LeaveRequestController], providers: [LeaveRequestsService], diff --git a/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts b/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts index 5991686..ce4807b 100644 --- a/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts +++ b/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts @@ -1,15 +1,15 @@ import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/leave-requests/dtos/upsert-leave-request.dto"; import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client"; -import { normalizeDates, toDateOnly } from "src/time-and-attendance/shared/helpers/date-time.helpers"; import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; import { LeaveRequestViewDto } from "src/time-and-attendance/leave-requests/dtos/leave-request-view.dto"; import { LeaveRequestsUtils } from "src/time-and-attendance/leave-requests/utils/leave-request.util"; -import { BankCodesResolver } from "src/time-and-attendance/shared/utils/resolve-bank-type-id.utils"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service"; import { PrismaService } from "src/prisma/prisma.service"; import { mapRowToView } from "src/time-and-attendance/leave-requests/mappers/leave-requests.mapper"; +import { normalizeDates, toDateOnly } from "src/time-and-attendance/utils/date-time.utils"; diff --git a/src/time-and-attendance/leave-requests/services/leave-request.service.ts b/src/time-and-attendance/leave-requests/services/leave-request.service.ts index 6390a10..499c2d8 100644 --- a/src/time-and-attendance/leave-requests/services/leave-request.service.ts +++ b/src/time-and-attendance/leave-requests/services/leave-request.service.ts @@ -1,18 +1,18 @@ import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; -import { normalizeDates, toDateOnly, toISODateKey } from "src/time-and-attendance/shared/helpers/date-time.helpers"; import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto"; import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client"; import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; import { roundToQuarterHour } from "src/common/utils/date-utils"; import { LeaveRequestsUtils } from "src/time-and-attendance/leave-requests/utils/leave-request.util"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; -import { BankCodesResolver } from "src/time-and-attendance/shared/utils/resolve-bank-type-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service"; import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service"; import { PrismaService } from "src/prisma/prisma.service"; import { mapRowToView } from "../mappers/leave-requests.mapper"; +import { normalizeDates, toDateOnly, toISODateKey } from "src/time-and-attendance/utils/date-time.utils"; @Injectable() export class LeaveRequestsService { constructor( diff --git a/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts b/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts index 629dbff..1cff7a8 100644 --- a/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts +++ b/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts @@ -1,15 +1,15 @@ import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/leave-requests/dtos/upsert-leave-request.dto"; import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client"; -import { normalizeDates, toDateOnly } from "src/time-and-attendance/shared/helpers/date-time.helpers"; import { LeaveRequestViewDto } from "src/time-and-attendance/leave-requests/dtos/leave-request-view.dto"; import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; import { roundToQuarterHour } from "src/common/utils/date-utils"; -import { BankCodesResolver } from "src/time-and-attendance/shared/utils/resolve-bank-type-id.utils"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service"; import { PrismaService } from "src/prisma/prisma.service"; import { mapRowToView } from "src/time-and-attendance/leave-requests/mappers/leave-requests.mapper"; +import { normalizeDates, toDateOnly } from "src/time-and-attendance/utils/date-time.utils"; diff --git a/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts b/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts index d6f16a5..2e71c39 100644 --- a/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts +++ b/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts @@ -1,15 +1,15 @@ import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/leave-requests/dtos/upsert-leave-request.dto"; import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client"; -import { normalizeDates, toDateOnly } from "src/time-and-attendance/shared/helpers/date-time.helpers"; import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; import { LeaveRequestViewDto } from "src/time-and-attendance/leave-requests/dtos/leave-request-view.dto"; import { roundToQuarterHour } from "src/common/utils/date-utils"; -import { BankCodesResolver } from "src/time-and-attendance/shared/utils/resolve-bank-type-id.utils"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; import { PrismaService } from "src/prisma/prisma.service"; import { mapRowToView } from "src/time-and-attendance/leave-requests/mappers/leave-requests.mapper"; +import { normalizeDates, toDateOnly } from "src/time-and-attendance/utils/date-time.utils"; @Injectable() diff --git a/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts b/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts index 61026c6..65ed977 100644 --- a/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts +++ b/src/time-and-attendance/pay-period/controllers/pay-periods.controller.ts @@ -1,5 +1,4 @@ -import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query } from "@nestjs/common"; -import { ApiNotFoundResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Query, Req } from "@nestjs/common"; import { PayPeriodDto } from "../dtos/pay-period.dto"; import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { PayPeriodsQueryService } from "../services/pay-periods-query.service"; @@ -9,7 +8,7 @@ import { Roles as RoleEnum } from '.prisma/client'; import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto"; import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto"; -@ApiTags('pay-periods') + @Controller('pay-periods') export class PayPeriodsController { @@ -19,9 +18,6 @@ export class PayPeriodsController { ) {} @Get('current-and-all') - @ApiOperation({summary: 'Return current pay period and the full list'}) - @ApiQuery({name: 'date', required:false, example: '2025-08-11', description:'Override for resolving the current period'}) - @ApiResponse({status: 200, description:'Find current and all pay periods', type: PayPeriodBundleDto}) async getCurrentAndAll(@Query('date') date?: string): Promise { const [current, periods] = await Promise.all([ this.queryService.findCurrent(date), @@ -31,19 +27,11 @@ export class PayPeriodsController { } @Get("date/:date") - @ApiOperation({ summary: "Resolve a period by a date within it" }) - @ApiResponse({ status: 200, description: "Pay period found for the selected date", type: PayPeriodDto }) - @ApiNotFoundResponse({ description: "Pay period not found for the selected date" }) async findByDate(@Param("date") date: string) { return this.queryService.findByDate(date); } @Get(":year/:periodNumber") - @ApiOperation({ summary: "Find pay period by year and period number" }) - @ApiParam({ name: "year", type: Number, example: 2024 }) - @ApiParam({ name: "periodNumber", type: Number, example: 1, description: "1..26" }) - @ApiResponse({ status: 200, description: "Pay period found", type: PayPeriodDto }) - @ApiNotFoundResponse({ description: "Pay period not found" }) async findOneByYear( @Param("year", ParseIntPipe) year: number, @Param("periodNumber", ParseIntPipe) period_no: number, @@ -61,30 +49,19 @@ export class PayPeriodsController { @Get(':year/:periodNumber/:email') //@RolesAllowed(RoleEnum.SUPERVISOR) - @ApiOperation({ summary: 'Supervisor crew overview for a given pay period' }) - @ApiParam({ name: 'year', type: Number, example: 2024 }) - @ApiParam({ name: 'periodNumber', type: Number, example: 1, description: '1..26' }) - @ApiQuery({ name: 'includeSubtree', required: false, type: Boolean, example: false, description: 'Include indirect reports' }) - @ApiResponse({ status: 200, description: 'Crew overview', type: PayPeriodOverviewDto }) - @ApiNotFoundResponse({ description: 'Pay period not found' }) - async getCrewOverview( - @Param('year', ParseIntPipe) year: number, - @Param('periodNumber', ParseIntPipe) period_no: number, - @Param('email') email: string, - @Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false, + async getCrewOverview( @Req() req, + @Param('year', ParseIntPipe) year: number, + @Param('periodNumber', ParseIntPipe) period_no: number, + @Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false, ): Promise { + const email = req.user?.email; return this.queryService.getCrewOverview(year, period_no, email, include_subtree); } @Get('overview/:year/:periodNumber') - @ApiOperation({ summary: 'Detailed view of a pay period by year + number' }) - @ApiParam({ name: 'year', type: Number, example: 2024 }) - @ApiParam({ name: 'periodNumber', type: Number, example: 1, description: '1..26' }) - @ApiResponse({ status: 200, description: 'Pay period overview found', type: PayPeriodOverviewDto }) - @ApiNotFoundResponse({ description: 'Pay period not found' }) - async getOverviewByYear( - @Param('year', ParseIntPipe) year: number, - @Param('periodNumber', ParseIntPipe) period_no: number, + async getOverviewByYear( + @Param('year', ParseIntPipe) year: number, + @Param('periodNumber', ParseIntPipe) period_no: number, ): Promise { return this.queryService.getOverviewByYearPeriod(year, period_no); } diff --git a/src/time-and-attendance/pay-period/dtos/bulk-crew-approval.dto.ts b/src/time-and-attendance/pay-period/dtos/bulk-crew-approval.dto.ts index 3762ddb..650927d 100644 --- a/src/time-and-attendance/pay-period/dtos/bulk-crew-approval.dto.ts +++ b/src/time-and-attendance/pay-period/dtos/bulk-crew-approval.dto.ts @@ -1,5 +1,5 @@ import { Type } from "class-transformer"; -import { IsArray, IsBoolean, IsEmail, IsInt, IsOptional, ValidateNested } from "class-validator"; +import { IsArray, IsBoolean, IsEmail, IsInt, ValidateNested } from "class-validator"; export class BulkCrewApprovalItemDto { @IsInt() diff --git a/src/time-and-attendance/pay-period/dtos/bundle-pay-period.dto.ts b/src/time-and-attendance/pay-period/dtos/bundle-pay-period.dto.ts index 9c5a61f..ad84088 100644 --- a/src/time-and-attendance/pay-period/dtos/bundle-pay-period.dto.ts +++ b/src/time-and-attendance/pay-period/dtos/bundle-pay-period.dto.ts @@ -1,11 +1,6 @@ -import { ApiProperty } from "@nestjs/swagger"; import { PayPeriodDto } from "./pay-period.dto"; export class PayPeriodBundleDto { - - @ApiProperty({ type: PayPeriodDto, description: 'Current pay period (resolved from date)' }) current: PayPeriodDto; - - @ApiProperty({ type: [PayPeriodDto], description: 'All pay periods' }) periods: PayPeriodDto[]; } \ No newline at end of file diff --git a/src/time-and-attendance/pay-period/dtos/overview-employee-period.dto.ts b/src/time-and-attendance/pay-period/dtos/overview-employee-period.dto.ts index 1ea6937..4cae27b 100644 --- a/src/time-and-attendance/pay-period/dtos/overview-employee-period.dto.ts +++ b/src/time-and-attendance/pay-period/dtos/overview-employee-period.dto.ts @@ -1,27 +1,7 @@ -import { ApiProperty } from '@nestjs/swagger'; - export class EmployeePeriodOverviewDto { - // @ApiProperty({ - // example: 42, - // description: "Employees.id (clé primaire num.)", - // }) - // @Allow() - // @IsOptional() - // employee_id: number; - - email: string; - - @ApiProperty({ - example: 'Alex Dupont', - description: 'Nom complet de lemployé', - }) employee_name: string; - - @ApiProperty({ example: 40, description: 'pay-period`s regular hours' }) regular_hours: number; - - @ApiProperty({ example: 0, description: 'pay-period`s other hours' }) other_hours: { evening_hours: number; @@ -35,20 +15,9 @@ export class EmployeePeriodOverviewDto { vacation_hours: number; }; - total_hours: number; - - @ApiProperty({ example: 420.69, description: 'pay-period`s total expenses ($)' }) expenses: number; - - @ApiProperty({ example: 40, description: 'pay-period total mileages (km)' }) mileage: number; - - @ApiProperty({ - example: true, - description: 'Tous les timesheets de la période sont approuvés pour cet employé', - }) is_approved: boolean; - is_remote: boolean; } diff --git a/src/time-and-attendance/pay-period/dtos/overview-pay-period.dto.ts b/src/time-and-attendance/pay-period/dtos/overview-pay-period.dto.ts index 041fba3..3748eb8 100644 --- a/src/time-and-attendance/pay-period/dtos/overview-pay-period.dto.ts +++ b/src/time-and-attendance/pay-period/dtos/overview-pay-period.dto.ts @@ -1,46 +1,11 @@ -import { ApiProperty } from '@nestjs/swagger'; import { EmployeePeriodOverviewDto } from './overview-employee-period.dto'; export class PayPeriodOverviewDto { - @ApiProperty({ example: 1, description: 'Period number (1–26)' }) pay_period_no: number; - - @ApiProperty({ example: 2023, description: 'Calendar year of the period' }) pay_year: number; - - @ApiProperty({ - example: '2023-12-17', - type: String, - format: 'date', - description: "Period start date (YYYY-MM-DD)", - }) period_start: string; - - @ApiProperty({ - example: '2023-12-30', - type: String, - format: 'date', - description: "Period end date (YYYY-MM-DD)", - }) period_end: string; - - @ApiProperty({ - example: '2023-12-30', - type: String, - format: 'date', - description: "Period pay day(YYYY-MM-DD)", - }) payday: string; - - @ApiProperty({ - example: '2023-12-17 → 2023-12-30', - description: 'Human-readable label', - }) label: string; - - @ApiProperty({ - type: [EmployeePeriodOverviewDto], - description: 'Per-employee overview for the period', - }) employees_overview: EmployeePeriodOverviewDto[]; } diff --git a/src/time-and-attendance/pay-period/dtos/pay-period.dto.ts b/src/time-and-attendance/pay-period/dtos/pay-period.dto.ts index 4f7989b..a85f481 100644 --- a/src/time-and-attendance/pay-period/dtos/pay-period.dto.ts +++ b/src/time-and-attendance/pay-period/dtos/pay-period.dto.ts @@ -1,25 +1,8 @@ -import { ApiProperty } from "@nestjs/swagger"; - export class PayPeriodDto { - @ApiProperty({ example: 1, - description: 'numéro cyclique de la période entre 1 et 26' }) pay_period_no: number; - - @ApiProperty({ example: '2023-12-17', - type: String, format: 'date' }) period_start: string; - - @ApiProperty({ example: '2023-12-30', - type: String, format: 'date' }) period_end: string; - - @ApiProperty({ example: '2023-01-04', - type: String, format: 'date' }) payday: string; - - @ApiProperty({ example: 2023 }) pay_year: number; - - @ApiProperty({ example: '2023-12-17 → 2023-12-30' }) label: string; } \ No newline at end of file diff --git a/src/time-and-attendance/shared/helpers/date-time.helpers.ts b/src/time-and-attendance/shared/helpers/date-time.helpers.ts deleted file mode 100644 index 2076530..0000000 --- a/src/time-and-attendance/shared/helpers/date-time.helpers.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { BadRequestException } from "@nestjs/common"; - -export const hhmmFromLocal = (d: Date) => - `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`; - -export const toDateOnly = (s: string): Date => { - if (/^\d{4}-\d{2}-\d{2}$/.test(s)) { - const y = Number(s.slice(0,4)); - const m = Number(s.slice(5,7)) - 1; - const d = Number(s.slice(8,10)); - return new Date(y, m, d, 0, 0, 0, 0); - } - const dt = new Date(s); - if (Number.isNaN(dt.getTime())) throw new BadRequestException(`Invalid date: ${s}`); - return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), 0,0,0,0); -}; - -// export const toStringFromDate = (d: Date) => -// `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; - - -export const toISOtoDateOnly = (iso: string): Date => { - const date = new Date(iso); - if (Number.isNaN(date.getTime())) { - throw new BadRequestException(`Invalid date: ${iso}`); - } - date.setHours(0, 0, 0, 0); - return date; -}; - -export const toISODateKey = (date: Date): string => date.toISOString().slice(0, 10); - -export const normalizeDates = (dates: string[]): string[] => - Array.from(new Set(dates.map((iso) => toISODateKey(toISOtoDateOnly(iso))))); \ No newline at end of file diff --git a/src/time-and-attendance/shared/interfaces/shifts.interface.ts b/src/time-and-attendance/shared/interfaces/shifts.interface.ts deleted file mode 100644 index 40f897e..0000000 --- a/src/time-and-attendance/shared/interfaces/shifts.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ShiftKey { - timesheet_id: number; - date: Date; - start_time: Date; - end_time: Date; - bank_code_id: number; - is_remote: boolean; - comment?: string | null; -} \ No newline at end of file diff --git a/src/time-and-attendance/shared/selects/expenses.select.ts b/src/time-and-attendance/shared/selects/expenses.select.ts deleted file mode 100644 index 540d98f..0000000 --- a/src/time-and-attendance/shared/selects/expenses.select.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const EXPENSE_SELECT = { - date: true, - amount: true, - mileage: true, - comment: true, - is_approved: true, - supervisor_comment: true, - bank_code: { select: { type: true } }, -} as const; - -export const EXPENSE_ASC_ORDER = { date: 'asc' as const }; \ No newline at end of file diff --git a/src/time-and-attendance/shared/selects/pay-periods.select.ts b/src/time-and-attendance/shared/selects/pay-periods.select.ts deleted file mode 100644 index a76f09b..0000000 --- a/src/time-and-attendance/shared/selects/pay-periods.select.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const PAY_PERIOD_SELECT = { - period_start: true, - period_end: true, -} as const; \ No newline at end of file diff --git a/src/time-and-attendance/shared/selects/shifts.select.ts b/src/time-and-attendance/shared/selects/shifts.select.ts deleted file mode 100644 index 8c738e1..0000000 --- a/src/time-and-attendance/shared/selects/shifts.select.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const SHIFT_SELECT = { - date: true, - start_time: true, - end_time: true, - comment: true, - is_approved: true, - is_remote: true, - bank_code: {select: { type: true } }, -} as const; - -export const SHIFT_ASC_ORDER = [{date: 'asc' as const}, {start_time: 'asc' as const}]; - diff --git a/src/time-and-attendance/shared/shared.module.ts b/src/time-and-attendance/shared/shared.module.ts deleted file mode 100644 index 0e26d7b..0000000 --- a/src/time-and-attendance/shared/shared.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { EmployeeTimesheetResolver } from "./utils/resolve-timesheet.utils"; -import { EmailToIdResolver } from "./utils/resolve-email-id.utils"; -import { BankCodesResolver } from "./utils/resolve-bank-type-id.utils"; -import { FullNameResolver } from "./utils/resolve-full-name.utils"; -import { PrismaModule } from "src/prisma/prisma.module"; -import { Module } from "@nestjs/common"; - -@Module({ -imports: [PrismaModule], -providers: [ - FullNameResolver, - EmailToIdResolver, - BankCodesResolver, - EmployeeTimesheetResolver, -], -exports: [ - FullNameResolver, - EmailToIdResolver, - BankCodesResolver, - EmployeeTimesheetResolver, -], -}) export class SharedModule {} \ No newline at end of file diff --git a/src/time-and-attendance/time-and-attendance.module.ts b/src/time-and-attendance/time-and-attendance.module.ts index 89de655..1d052df 100644 --- a/src/time-and-attendance/time-and-attendance.module.ts +++ b/src/time-and-attendance/time-and-attendance.module.ts @@ -4,7 +4,6 @@ import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-l import { ExpenseController } from "src/time-and-attendance/expenses/controllers/expense.controller"; import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service"; import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module"; -import { SharedModule } from "src/time-and-attendance/shared/shared.module"; import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller"; import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service"; import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service"; @@ -14,16 +13,17 @@ import { ShiftsGetService } from "src/time-and-attendance/time-tracker/shifts/se import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service"; import { TimesheetController } from "src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller"; import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @Module({ imports: [ - BusinessLogicsModule, - PayperiodsModule, - SharedModule, + BusinessLogicsModule, + PayperiodsModule, ], controllers: [ - TimesheetController, - ShiftController, + TimesheetController, + ShiftController, SchedulePresetsController, ExpenseController, ], @@ -35,6 +35,8 @@ import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-track SchedulePresetsUpsertService, SchedulePresetsGetService, SchedulePresetsApplyService, + EmailToIdResolver, + BankCodesResolver, ], exports: [], -}) export class TimeAndAttendanceModule{}; \ No newline at end of file +}) export class TimeAndAttendanceModule { }; \ No newline at end of file diff --git a/src/time-and-attendance/time-tracker/schedule-presets/schedule-presets.module.ts b/src/time-and-attendance/time-tracker/schedule-presets/schedule-presets.module.ts index b261a2f..b17863a 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/schedule-presets.module.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/schedule-presets.module.ts @@ -1,6 +1,5 @@ import { Module } from "@nestjs/common"; -import { SharedModule } from "src/time-and-attendance/shared/shared.module"; import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller"; import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service"; import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service"; @@ -8,7 +7,6 @@ import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-track @Module({ - imports: [SharedModule], controllers: [SchedulePresetsController], providers: [ SchedulePresetsUpsertService, diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts index 9815e10..c055373 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts @@ -4,7 +4,7 @@ import { DATE_ISO_FORMAT } from "src/time-and-attendance/utils/constants.utils"; import { PrismaService } from "src/prisma/prisma.service"; import { ApplyResult } from "src/time-and-attendance/utils/type.utils"; import { WEEKDAY } from "src/time-and-attendance/utils/mappers.utils"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @Injectable() diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts index 8b89c80..3bb3050 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts @@ -2,7 +2,7 @@ import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/typ import { PrismaService } from "src/prisma/prisma.service"; import { Injectable } from "@nestjs/common"; import { Prisma } from "@prisma/client"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @Injectable() export class SchedulePresetsGetService { diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts index 534082e..0eaef09 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts @@ -3,9 +3,9 @@ import { CreatePresetResult, DeletePresetResult, UpdatePresetResult } from "src/ import { Prisma, Weekday } from "@prisma/client"; import { toHHmmFromDate } from "src/time-and-attendance/utils/date-time.utils"; import { PrismaService } from "src/prisma/prisma.service"; -import { BankCodesResolver } from "src/time-and-attendance/shared/utils/resolve-bank-type-id.utils"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; import { SchedulePresetsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @Injectable() export class SchedulePresetsUpsertService { diff --git a/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts b/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts index 44261cb..59dc811 100644 --- a/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts +++ b/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts @@ -3,7 +3,7 @@ import { overlaps, toStringFromHHmm, toStringFromDate, toDateFromString, toHHmmF import { Injectable, BadRequestException, ConflictException, NotFoundException } from "@nestjs/common"; import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service"; import { PrismaService } from "src/prisma/prisma.service"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto"; import { GetShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto"; import { UpdateShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-update.dto"; diff --git a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts index 6ea81e2..ef7fdd6 100644 --- a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts +++ b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts @@ -3,7 +3,7 @@ import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/time-and-attendance/utils/co import { Injectable, NotFoundException } from "@nestjs/common"; import { TotalExpenses, TotalHours } from "src/time-and-attendance/utils/type.utils"; import { PrismaService } from "src/prisma/prisma.service"; -import { EmailToIdResolver } from "src/time-and-attendance/shared/utils/resolve-email-id.utils"; +import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @Injectable() export class GetTimesheetsOverviewService { diff --git a/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts b/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts index f559f6d..a12322a 100644 --- a/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts +++ b/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts @@ -1,12 +1,10 @@ import { Module } from '@nestjs/common'; -import { SharedModule } from 'src/time-and-attendance/shared/shared.module'; import { TimesheetController } from 'src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller'; import { TimesheetArchiveService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-archive.service'; import { GetTimesheetsOverviewService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service'; @Module({ - imports: [SharedModule], controllers: [TimesheetController], providers: [ TimesheetArchiveService, diff --git a/src/time-and-attendance/utils/constants.utils.ts b/src/time-and-attendance/utils/constants.utils.ts index e2f20fc..2a53b28 100644 --- a/src/time-and-attendance/utils/constants.utils.ts +++ b/src/time-and-attendance/utils/constants.utils.ts @@ -6,6 +6,8 @@ export const ANCHOR_ISO = '2023-12-17'; export const PERIOD_DAYS = 14; export const PERIODS_PER_YEAR = 26; export const MS_PER_DAY = 86_400_000; +export const MS_PER_WEEK = 7 * 24 * 60 * 60 * 1000; + //REGEX CONSTANTS export const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/; diff --git a/src/time-and-attendance/utils/date-time.utils.ts b/src/time-and-attendance/utils/date-time.utils.ts index 07ea016..cfba4e8 100644 --- a/src/time-and-attendance/utils/date-time.utils.ts +++ b/src/time-and-attendance/utils/date-time.utils.ts @@ -1,3 +1,4 @@ +import { BadRequestException } from "@nestjs/common"; import { ANCHOR_ISO, MS_PER_DAY, PERIODS_PER_YEAR, PERIOD_DAYS } from "src/time-and-attendance/utils/constants.utils"; //ensures the week starts from sunday @@ -88,4 +89,38 @@ export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) { } export const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) => - !(a.end <= b.start || a.start >= b.end); \ No newline at end of file + !(a.end <= b.start || a.start >= b.end); + + +export const hhmmFromLocal = (d: Date) => + `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`; + +export const toDateOnly = (s: string): Date => { + if (/^\d{4}-\d{2}-\d{2}$/.test(s)) { + const y = Number(s.slice(0,4)); + const m = Number(s.slice(5,7)) - 1; + const d = Number(s.slice(8,10)); + return new Date(y, m, d, 0, 0, 0, 0); + } + const dt = new Date(s); + if (Number.isNaN(dt.getTime())) throw new BadRequestException(`Invalid date: ${s}`); + return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), 0,0,0,0); +}; + +// export const toStringFromDate = (d: Date) => +// `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + + +export const toISOtoDateOnly = (iso: string): Date => { + const date = new Date(iso); + if (Number.isNaN(date.getTime())) { + throw new BadRequestException(`Invalid date: ${iso}`); + } + date.setHours(0, 0, 0, 0); + return date; +}; + +export const toISODateKey = (date: Date): string => date.toISOString().slice(0, 10); + +export const normalizeDates = (dates: string[]): string[] => + Array.from(new Set(dates.map((iso) => toISODateKey(toISOtoDateOnly(iso))))); \ No newline at end of file diff --git a/src/time-and-attendance/shared/utils/resolve-bank-type-id.utils.ts b/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts similarity index 100% rename from src/time-and-attendance/shared/utils/resolve-bank-type-id.utils.ts rename to src/time-and-attendance/utils/resolve-bank-type-id.utils.ts diff --git a/src/time-and-attendance/shared/utils/resolve-email-id.utils.ts b/src/time-and-attendance/utils/resolve-email-id.utils.ts similarity index 100% rename from src/time-and-attendance/shared/utils/resolve-email-id.utils.ts rename to src/time-and-attendance/utils/resolve-email-id.utils.ts diff --git a/src/time-and-attendance/shared/utils/resolve-full-name.utils.ts b/src/time-and-attendance/utils/resolve-full-name.utils.ts similarity index 100% rename from src/time-and-attendance/shared/utils/resolve-full-name.utils.ts rename to src/time-and-attendance/utils/resolve-full-name.utils.ts diff --git a/src/time-and-attendance/shared/utils/resolve-shifts-id.utils.ts b/src/time-and-attendance/utils/resolve-shifts-id.utils.ts similarity index 93% rename from src/time-and-attendance/shared/utils/resolve-shifts-id.utils.ts rename to src/time-and-attendance/utils/resolve-shifts-id.utils.ts index 4d9d313..e76d144 100644 --- a/src/time-and-attendance/shared/utils/resolve-shifts-id.utils.ts +++ b/src/time-and-attendance/utils/resolve-shifts-id.utils.ts @@ -1,7 +1,7 @@ import { Prisma, PrismaClient } from "@prisma/client"; import { NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { ShiftKey } from "../interfaces/shifts.interface"; +import { ShiftKey } from "src/time-and-attendance/utils/type.utils"; type Tx = Prisma.TransactionClient | PrismaClient; diff --git a/src/time-and-attendance/shared/utils/resolve-timesheet.utils.ts b/src/time-and-attendance/utils/resolve-timesheet.utils.ts similarity index 100% rename from src/time-and-attendance/shared/utils/resolve-timesheet.utils.ts rename to src/time-and-attendance/utils/resolve-timesheet.utils.ts diff --git a/src/time-and-attendance/utils/selects.utils.ts b/src/time-and-attendance/utils/selects.utils.ts index 3dbf127..fd4a356 100644 --- a/src/time-and-attendance/utils/selects.utils.ts +++ b/src/time-and-attendance/utils/selects.utils.ts @@ -47,3 +47,34 @@ export const leaveRequestsSelect = { } }, } satisfies Prisma.LeaveRequestsSelect; + + +export const EXPENSE_SELECT = { + date: true, + amount: true, + mileage: true, + comment: true, + is_approved: true, + supervisor_comment: true, + bank_code: { select: { type: true } }, +} as const; + +export const EXPENSE_ASC_ORDER = { date: 'asc' as const }; + +export const PAY_PERIOD_SELECT = { + period_start: true, + period_end: true, +} as const; + +export const SHIFT_SELECT = { + date: true, + start_time: true, + end_time: true, + comment: true, + is_approved: true, + is_remote: true, + bank_code: {select: { type: true } }, +} as const; + +export const SHIFT_ASC_ORDER = [{date: 'asc' as const}, {start_time: 'asc' as const}]; + diff --git a/src/time-and-attendance/utils/type.utils.ts b/src/time-and-attendance/utils/type.utils.ts index 5e846c1..412566a 100644 --- a/src/time-and-attendance/utils/type.utils.ts +++ b/src/time-and-attendance/utils/type.utils.ts @@ -1,5 +1,4 @@ -import { Prisma } from "@prisma/client"; -import { WeekOvertimeSummary } from "src/time-and-attendance/domains/services/overtime.service"; +import { Prisma, PrismaClient } from "@prisma/client"; import { GetExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-get.dto"; import { updateExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-update.dto"; import { SchedulePresetsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; @@ -80,4 +79,32 @@ export type ApplyResult = { export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>; -export type UpsertAction = 'create' | 'update' | 'delete'; \ No newline at end of file +export type UpsertAction = 'create' | 'update' | 'delete'; + +export type Tx = Prisma.TransactionClient | PrismaClient; + +export type WeekOvertimeSummary = { + week_start:string; + week_end: string; + week_total_hours: number; + weekly_overtime: number; + daily_overtime_kept: number; + total_overtime: number; + breakdown: Array<{ + date:string; + day_hours: number; + day_overtime: number; + daily_kept: number; + running_total_before: number; + }>; +}; + +export interface ShiftKey { + timesheet_id: number; + date: Date; + start_time: Date; + end_time: Date; + bank_code_id: number; + is_remote: boolean; + comment?: string | null; +} \ No newline at end of file