clean(folder): cleaning imports
This commit is contained in:
parent
c59b50a829
commit
f1f765b350
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pay period not found for the selected date"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pay period not found"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pay period not found"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Pay period not found"
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<number> {
|
||||
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<number> {
|
||||
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<number> {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<number> {
|
||||
// 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<number> {
|
||||
// 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<void> {
|
||||
// //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`);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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";
|
||||
|
||||
|
|
@ -9,7 +8,6 @@ import { Module } from "@nestjs/common";
|
|||
imports: [
|
||||
BusinessLogicsModule,
|
||||
ShiftsModule,
|
||||
SharedModule
|
||||
],
|
||||
controllers: [LeaveRequestController],
|
||||
providers: [LeaveRequestsService],
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<PayPeriodBundleDto> {
|
||||
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,27 +49,16 @@ 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(
|
||||
async getCrewOverview( @Req() req,
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||
@Param('email') email: string,
|
||||
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false,
|
||||
): Promise<PayPeriodOverviewDto> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)))));
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 };
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export const PAY_PERIOD_SELECT = {
|
||||
period_start: true,
|
||||
period_end: true,
|
||||
} as const;
|
||||
|
|
@ -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}];
|
||||
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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,12 +13,13 @@ 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,
|
||||
],
|
||||
controllers: [
|
||||
TimesheetController,
|
||||
|
|
@ -35,6 +35,8 @@ import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-track
|
|||
SchedulePresetsUpsertService,
|
||||
SchedulePresetsGetService,
|
||||
SchedulePresetsApplyService,
|
||||
EmailToIdResolver,
|
||||
BankCodesResolver,
|
||||
],
|
||||
exports: [],
|
||||
}) export class TimeAndAttendanceModule { };
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}$/;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -89,3 +90,37 @@ 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);
|
||||
|
||||
|
||||
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)))));
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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}];
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -81,3 +80,31 @@ export type ApplyResult = {
|
|||
export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;
|
||||
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user