clean(modules): modules file cleaning

This commit is contained in:
Matthieu Haineault 2025-12-04 14:50:48 -05:00
parent 13962a8496
commit 6d3ff46c35
35 changed files with 0 additions and 2502 deletions

View File

@ -1,79 +0,0 @@
// import { BadRequestException, Injectable, Logger } from "@nestjs/common";
// import { PrismaService } from "../prisma/prisma.service";
// //THIS SERVICE IS NOT USED, RULES TO BE DETERMINED WITH MIKE/HR/ACCOUNTING
// @Injectable()
// export class AfterHoursService {
// private readonly logger = new Logger(AfterHoursService.name);
// private static readonly BUSINESS_START = 7;
// private static readonly BUSINESS_END = 18;
// private static readonly ROUND_MINUTES = 15;
// constructor(private readonly prisma: PrismaService) {}
// private getPreBusinessMinutes(start: Date, end: Date): number {
// const biz_start = new Date(start);
// biz_start.setHours(AfterHoursService.BUSINESS_START, 0,0,0);
// if (end>= start || start >= biz_start) {
// return 0;
// }
// const segment_end = end < biz_start ? end : biz_start;
// const minutes = (segment_end.getTime() - start.getTime()) / 60000;
// this.logger.debug(`getPreBusinessMintutes -> ${minutes.toFixed(1)}min`);
// return minutes;
// }
// private getPostBusinessMinutes(start: Date, end: Date): number {
// const biz_end = new Date(start);
// biz_end.setHours(AfterHoursService.BUSINESS_END,0,0,0);
// if( end <= biz_end ) {
// return 0;
// }
// const segment_start = start > biz_end ? start : biz_end;
// const minutes = (end.getTime() - segment_start.getTime()) / 60000;
// this.logger.debug(`getPostBusinessMintutes -> ${minutes.toFixed(1)}min`);
// return minutes;
// }
// private roundToNearestQUarterMinute(minutes: number): number {
// const rounded = Math.round(minutes / AfterHoursService.ROUND_MINUTES)
// * AfterHoursService.ROUND_MINUTES;
// this.logger.debug(`roundToNearestQuarterMinute -> raw=${minutes.toFixed(1)}min, rounded= ${rounded}min`);
// return rounded;
// }
// public computeAfterHours(start: Date, end:Date): number {
// if(end.getTime() <= start.getTime()) {
// throw new BadRequestException('The end cannot be before the starting of the shift');
// }
// if (start.toDateString() !== end.toDateString()) {
// throw new BadRequestException('you cannot enter a shift that start in a day and end in the next' +
// 'You must create 2 instances, one on the first day and the second during the next day.');
// }
// const pre_min = this.getPreBusinessMinutes(start, end);
// const post_min = this.getPostBusinessMinutes(start, end);
// const raw_aftermin = pre_min + post_min;
// const rounded_min = this.roundToNearestQUarterMinute(raw_aftermin);
// const hours = rounded_min / 60;
// const result = parseFloat(hours.toFixed(2));
// this.logger.debug(`computeAfterHours -> raw_aftermin = ${raw_aftermin.toFixed(1)}min, +
// rounded = ${rounded_min}min, hours = ${result.toFixed(2)}`);
// return result;
// }
// }

View File

@ -1,66 +0,0 @@
// import { ApiProperty } from "@nestjs/swagger";
// import { Type } from "class-transformer";
// import { Allow, IsBoolean, IsDate, IsDateString, IsInt, IsNumber, IsOptional, IsString } from "class-validator";
// export class CreateExpenseDto {
// @ApiProperty({
// example: 1,
// description: 'Unique ID of the expense (auto-generated)',
// })
// @Allow()
// id?: number;
// @ApiProperty({
// example: 101,
// description: 'ID number for a set timesheet',
// })
// @Type(()=> Number)
// @IsInt()
// timesheet_id: number;
// @ApiProperty({
// example: 7,
// description: 'ID number of an bank code (link with bank-codes)',
// })
// @Type(() => Number)
// @IsInt()
// bank_code_id: number;
// @ApiProperty({
// example: '3018-10-20T00:00:00.000Z',
// description: 'Date where the expense was made',
// })
// @IsDateString()
// date: string;
// @ApiProperty({
// example: 17.82,
// description: 'amount in $ for a refund',
// })
// @Type(() => Number)
// @IsNumber()
// amount: number;
// @ApiProperty({
// example:'Spent for mileage between A and B',
// description:'explain`s why the expense was made'
// })
// @IsString()
// comment: string;
// @ApiProperty({
// example: 'DENIED, APPROUVED, PENDING, etc...',
// description: 'validation status',
// })
// @IsOptional()
// @IsBoolean()
// is_approved?: boolean;
// @ApiProperty({
// example:'Asked X to go there as an emergency response',
// description:'Supervisro`s justification for the spending of an employee'
// })
// @IsString()
// @IsOptional()
// supervisor_comment?: string;
// }

View File

@ -1,33 +0,0 @@
// import { Type } from "class-transformer";
// import { IsArray, IsOptional, IsString, Length, Matches, ValidateNested } from "class-validator";
// export class CreateTimesheetDto {
// @IsString()
// @Matches(/^\d{4}-\d{2}-\d{2}$/)
// date!: string;
// @IsString()
// @Length(1,64)
// type!: string;
// @IsString()
// @Matches(/^\d{2}:\d{2}$/)
// start_time!: string;
// @IsString()
// @Matches(/^\d{2}:\d{2}$/)
// end_time!: string;
// @IsOptional()
// @IsString()
// @Length(0,512)
// comment?: string;
// }
// export class CreateWeekShiftsDto {
// @IsArray()
// @ValidateNested({each:true})
// @Type(()=> CreateTimesheetDto)
// shifts!: CreateTimesheetDto[];
// }

View File

@ -1,2 +0,0 @@
// export const MS_PER_DAY = 86_400_000;
// export const MS_PER_HOUR = 3_600_000;

View File

@ -1,249 +0,0 @@
// import { BaseApprovalService } from "src/common/shared/base-approval.service";
// import { Expenses, Prisma } from "@prisma/client";
// import { PrismaService } from "src/prisma/prisma.service";
// import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
// import {
// BadRequestException,
// Injectable,
// NotFoundException
// } from "@nestjs/common";
// import {
// assertAndTrimComment,
// computeAmountDecimal,
// computeMileageAmount,
// mapDbExpenseToDayResponse,
// normalizeType,
// parseAttachmentId
// } from "../utils/expenses.utils";
// @Injectable()
// export class ExpensesCommandService extends BaseApprovalService<Expenses> {
// constructor(
// prisma: PrismaService,
// private readonly bankCodesResolver: BankCodesResolver,
// private readonly timesheetsResolver: EmployeeTimesheetResolver,
// private readonly emailResolver: EmailToIdResolver,
// ) { super(prisma); }
// //_____________________________________________________________________________________________
// // APPROVAL TX-DELEGATE METHODS
// //_____________________________________________________________________________________________
// protected get delegate() {
// return this.prisma.expenses;
// }
// protected delegateFor(transaction: Prisma.TransactionClient){
// return transaction.expenses;
// }
// async updateApproval(id: number, isApproved: boolean): Promise<Expenses> {
// return this.prisma.$transaction((transaction) =>
// this.updateApprovalWithTransaction(transaction, id, isApproved),
// );
// }
// //_____________________________________________________________________________________________
// // MASTER CRUD FUNCTION
// //_____________________________________________________________________________________________
// readonly upsertExpensesByDate = async (email: string, date: string, dto: UpsertExpenseDto,
// ): Promise<{ action:UpsertAction; day: ExpenseResponse[] }> => {
// //validates if there is an existing expense, at least 1 old or new
// const { old_expense, new_expense } = dto ?? {};
// if(!old_expense && !new_expense) throw new BadRequestException('At least one expense must be provided');
// //validate date format
// const date_only = toDateOnly(date);
// if(Number.isNaN(date_only.getTime())) throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)');
// //resolve employee_id by email
// const employee_id = await this.emailResolver.findIdByEmail(email);
// //make sure a timesheet existes
// const timesheet_id = await this.timesheetsResolver.findTimesheetIdByEmail(email, date_only);
// if(!timesheet_id) throw new NotFoundException(`no timesheet found for employee #${employee_id}`)
// const {id} = timesheet_id;
// return this.prisma.$transaction(async (tx) => {
// const loadDay = async (): Promise<ExpenseResponse[]> => {
// const rows = await tx.expenses.findMany({
// where: {
// timesheet_id: id,
// date: date_only,
// },
// include: {
// bank_code: {
// select: {
// type: true,
// },
// },
// },
// orderBy: [{ date: 'asc' }, { id: 'asc' }],
// });
// return rows.map((r) =>
// mapDbExpenseToDayResponse({
// date: r.date,
// amount: r.amount ?? 0,
// mileage: r.mileage ?? 0,
// comment: r.comment,
// is_approved: r.is_approved,
// bank_code: r.bank_code,
// }));
// };
// const normalizePayload = async (payload: {
// type: string;
// amount?: number;
// mileage?: number;
// comment: string;
// attachment?: string | number;
// }): Promise<{
// type: string;
// bank_code_id: number;
// amount: Prisma.Decimal;
// mileage: number | null;
// comment: string;
// attachment: number | null;
// }> => {
// const type = normalizeType(payload.type);
// const comment = assertAndTrimComment(payload.comment);
// const attachment = parseAttachmentId(payload.attachment);
// const { id: bank_code_id, modifier } = await this.bankCodesResolver.findByType(type);
// let amount = computeAmountDecimal(type, payload, modifier);
// let mileage: number | null = null;
// if (type === 'MILEAGE') {
// mileage = Number(payload.mileage ?? 0);
// if (!(mileage > 0)) {
// throw new BadRequestException('Mileage required and must be > 0 for type MILEAGE');
// }
// const amountNumber = computeMileageAmount(mileage, modifier);
// amount = new Prisma.Decimal(amountNumber);
// } else {
// if (!(typeof payload.amount === 'number' && payload.amount >= 0)) {
// throw new BadRequestException('Amount required for non-MILEAGE expense');
// }
// amount = new Prisma.Decimal(payload.amount);
// }
// if (attachment !== null) {
// const attachment_row = await tx.attachments.findUnique({
// where: { id: attachment },
// select: { status: true },
// });
// if (!attachment_row || attachment_row.status !== 'ACTIVE') {
// throw new BadRequestException('Attachment not found or inactive');
// }
// }
// return {
// type,
// bank_code_id,
// amount,
// mileage,
// comment,
// attachment
// };
// };
// const findExactOld = async (norm: {
// bank_code_id: number;
// amount: Prisma.Decimal;
// mileage: number | null;
// comment: string;
// attachment: number | null;
// }) => {
// return tx.expenses.findFirst({
// where: {
// timesheet_id: id,
// date: date_only,
// bank_code_id: norm.bank_code_id,
// amount: norm.amount,
// comment: norm.comment,
// attachment: norm.attachment,
// ...(norm.mileage !== null ? { mileage: norm.mileage } : { mileage: null }),
// },
// select: { id: true },
// });
// };
// let action : UpsertAction;
// //_____________________________________________________________________________________________
// // DELETE
// //_____________________________________________________________________________________________
// if(old_expense && !new_expense) {
// const old_norm = await normalizePayload(old_expense);
// const existing = await findExactOld(old_norm);
// if(!existing) {
// throw new NotFoundException({
// error_code: 'EXPENSE_STALE',
// message: 'The expense was modified or deleted by someone else',
// });
// }
// await tx.expenses.delete({where: { id: existing.id } });
// action = 'delete';
// }
// //_____________________________________________________________________________________________
// // CREATE
// //_____________________________________________________________________________________________
// else if (!old_expense && new_expense) {
// const new_exp = await normalizePayload(new_expense);
// await tx.expenses.create({
// data: {
// timesheet_id: id,
// date: date_only,
// bank_code_id: new_exp.bank_code_id,
// amount: new_exp.amount,
// mileage: new_exp.mileage,
// comment: new_exp.comment,
// attachment: new_exp.attachment,
// is_approved: false,
// },
// });
// action = 'create';
// }
// //_____________________________________________________________________________________________
// // UPDATE
// //_____________________________________________________________________________________________
// else if(old_expense && new_expense) {
// const old_norm = await normalizePayload(old_expense);
// const existing = await findExactOld(old_norm);
// if(!existing) {
// throw new NotFoundException({
// error_code: 'EXPENSE_STALE',
// message: 'The expense was modified or deleted by someone else',
// });
// }
// const new_exp = await normalizePayload(new_expense);
// await tx.expenses.update({
// where: { id: existing.id },
// data: {
// bank_code_id: new_exp.bank_code_id,
// amount: new_exp.amount,
// mileage: new_exp.mileage,
// comment: new_exp.comment,
// attachment: new_exp.attachment,
// },
// });
// action = 'update';
// }
// else {
// throw new BadRequestException('Invalid upsert combination');
// }
// const day = await loadDay();
// return { action, day };
// });
// }
// }

View File

@ -1,171 +0,0 @@
// import { Injectable, NotFoundException } from "@nestjs/common";
// import { PrismaService } from "src/prisma/prisma.service";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// @Injectable()
// export class ExpensesQueryService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly employeeRepo: EmailToIdResolver,
// ) {}
// //fetchs all expenses for a selected employee using email, pay-period-year and number
// async findExpenseListByPayPeriodAndEmail(
// email: string,
// year: number,
// period_no: number
// ): Promise<ExpenseListResponseDto> {
// //fetch employe_id using email
// const employee_id = await this.employeeRepo.findIdByEmail(email);
// if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
// //fetch pay-period using year and period_no
// const pay_period = await this.prisma.payPeriods.findFirst({
// where: {
// pay_year: year,
// pay_period_no: period_no
// },
// select: { period_start: true, period_end: true },
// });
// if(!pay_period) throw new NotFoundException(`Pay period ${year}- ${period_no} not found`);
// const start = toUTCDateOnly(pay_period.period_start);
// const end = toUTCDateOnly(pay_period.period_end);
// //sets rows data
// const rows = await this.prisma.expenses.findMany({
// where: {
// date: { gte: start, lte: end },
// timesheet: { is: { employee_id } },
// },
// orderBy: { date: 'asc'},
// select: {
// amount: true,
// mileage: true,
// comment: true,
// is_approved: true,
// supervisor_comment: true,
// bank_code: {select: { type: true } },
// },
// });
// //declare return values
// const expenses: ExpenseDto[] = [];
// let total_amount = 0;
// let total_mileage = 0;
// //set rows
// for(const row of rows) {
// const type = (row.bank_code?.type ?? '').toUpperCase();
// const amount = round2(Number(row.amount ?? 0));
// const mileage = round2(Number(row.mileage ?? 0));
// if(type === EXPENSE_TYPES.MILEAGE) {
// total_mileage += mileage;
// } else {
// total_amount += amount;
// }
// //fills rows array
// expenses.push({
// type,
// amount,
// mileage,
// comment: row.comment ?? '',
// is_approved: row.is_approved ?? false,
// supervisor_comment: row.supervisor_comment ?? '',
// });
// }
// return {
// expenses,
// total_expense: round2(total_amount),
// total_mileage: round2(total_mileage),
// };
// }
// //_____________________________________________________________________________________________
// // Deprecated or unused methods
// //_____________________________________________________________________________________________
// // async create(dto: CreateExpenseDto): Promise<Expenses> {
// // const { timesheet_id, bank_code_id, date, amount:rawAmount,
// // comment, is_approved,supervisor_comment} = dto;
// // //fetches type and modifier
// // const bank_code = await this.prisma.bankCodes.findUnique({
// // where: { id: bank_code_id },
// // select: { type: true, modifier: true },
// // });
// // if(!bank_code) throw new NotFoundException(`bank_code #${bank_code_id} not found`);
// // //if mileage -> service, otherwise the ratio is amount:1
// // let final_amount: number;
// // if(bank_code.type === 'mileage') {
// // final_amount = await this.mileageService.calculateReimbursement(rawAmount, bank_code_id);
// // }else {
// // final_amount = parseFloat( (rawAmount * bank_code.modifier).toFixed(2));
// // }
// // return this.prisma.expenses.create({
// // data: {
// // timesheet_id,
// // bank_code_id,
// // date,
// // amount: final_amount,
// // comment,
// // is_approved,
// // supervisor_comment
// // },
// // include: { timesheet: { include: { employee: { include: { user: true }}}},
// // bank_code: true,
// // },
// // })
// // }
// // async findAll(filters: SearchExpensesDto): Promise<Expenses[]> {
// const where = buildPrismaWhere(filters);
// // const expenses = await this.prisma.expenses.findMany({ where })
// // return expenses;
// // }
// // async findOne(id: number): Promise<Expenses> {
// // const expense = await this.prisma.expenses.findUnique({
// // where: { id },
// // include: { timesheet: { include: { employee: { include: { user:true } } } },
// // bank_code: true,
// // },
// // });
// // if (!expense) {
// // throw new NotFoundException(`Expense #${id} not found`);
// // }
// // return expense;
// // }
// // async update(id: number, dto: UpdateExpenseDto): Promise<Expenses> {
// // await this.findOne(id);
// // const { timesheet_id, bank_code_id, date, amount,
// // comment, is_approved, supervisor_comment} = dto;
// // return this.prisma.expenses.update({
// // where: { id },
// // data: {
// // ...(timesheet_id !== undefined && { timesheet_id}),
// // ...(bank_code_id !== undefined && { bank_code_id }),
// // ...(date !== undefined && { date }),
// // ...(amount !== undefined && { amount }),
// // ...(comment !== undefined && { comment }),
// // ...(is_approved !== undefined && { is_approved }),
// // ...(supervisor_comment !== undefined && { supervisor_comment }),
// // },
// // include: { timesheet: { include: { employee: { include: { user: true } } } },
// // bank_code: true,
// // },
// // });
// // }
// // async remove(id: number): Promise<Expenses> {
// // await this.findOne(id);
// // return this.prisma.expenses.delete({ where: { id } });
// // }
// }

View File

@ -1,95 +0,0 @@
// import { Body, Controller, Get, Param, Put, } from "@nestjs/common";
// import { Roles as RoleEnum } from '.prisma/client';
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
// import { ExpensesCommandService } from "../services/expenses-command.service";
// import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
// import { UpsertExpenseResult } from "../types and interfaces/expenses.types.interfaces";
// import { DayExpensesDto } from "src/modules/timesheets/~misc_deprecated-files/timesheet-period.dto";
// import { ExpensesQueryService } from "../services/expenses-query.service";
// @ApiTags('Expenses')
// @ApiBearerAuth('access-token')
// // @UseGuards()
// @Controller('Expenses')
// export class ExpensesController {
// constructor(
// private readonly query: ExpensesQueryService,
// private readonly command: ExpensesCommandService,
// ) {}
// @Put('upsert/:email/:date')
// async upsert_by_date(
// @Param('email') email: string,
// @Param('date') date: string,
// @Body() dto: UpsertExpenseDto,
// ): Promise<UpsertExpenseResult> {
// return this.command.upsertExpensesByDate(email, date, dto);
// }
// @Get('list/:email/:year/:period_no')
// async findExpenseListByPayPeriodAndEmail(
// @Param('email') email:string,
// @Param('year') year: number,
// @Param('period_no') period_no: number,
// ): Promise<DayExpensesDto> {
// return this.query.findExpenseListByPayPeriodAndEmail(email, year, period_no);
// }
// //_____________________________________________________________________________________________
// // Deprecated or unused methods
// //_____________________________________________________________________________________________
// // @Post()
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Create expense' })
// // @ApiResponse({ status: 201, description: 'Expense created',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
// // create(@Body() dto: CreateExpenseDto): Promise<Expenses> {
// // return this.query.create(dto);
// // }
// // @Get()
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Find all expenses' })
// // @ApiResponse({ status: 201, description: 'List of expenses found',type: CreateExpenseDto, isArray: true })
// // @ApiResponse({ status: 400, description: 'List of expenses not found' })
// // @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
// // findAll(@Query() filters: SearchExpensesDto): Promise<Expenses[]> {
// // return this.query.findAll(filters);
// // }
// // @Get(':id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Find expense' })
// // @ApiResponse({ status: 201, description: 'Expense found',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Expense not found' })
// // findOne(@Param('id', ParseIntPipe) id: number): Promise <Expenses> {
// // return this.query.findOne(id);
// // }
// // @Patch(':id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Expense shift' })
// // @ApiResponse({ status: 201, description: 'Expense updated',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Expense not found' })
// // update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateExpenseDto) {
// // return this.query.update(id,dto);
// // }
// // @Delete(':id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Delete expense' })
// // @ApiResponse({ status: 201, description: 'Expense deleted',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Expense not found' })
// // remove(@Param('id', ParseIntPipe) id: number): Promise<Expenses> {
// // return this.query.remove(id);
// // }
// // @Patch('approval/:id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
// // return this.command.updateApproval(id, isApproved);
// // }
// }

View File

@ -1,14 +0,0 @@
// export type UpsertAction = 'create' | 'update' | 'delete';
// export interface ExpenseResponse {
// date: string;
// type: string;
// amount: number;
// comment: string;
// is_approved: boolean;
// };
// export type UpsertExpenseResult = {
// action: UpsertAction;
// day: ExpenseResponse[]
// };

View File

@ -1,111 +0,0 @@
// import { BadRequestException } from "@nestjs/common";
// import { ExpenseResponse } from "../types and interfaces/expenses.types.interfaces";
// import { Prisma } from "@prisma/client";
// //uppercase and trim for validation
// export function normalizeType(type: string): string {
// return (type ?? '').trim().toUpperCase();
// };
// //required comment after trim
// export function assertAndTrimComment(comment: string): string {
// const cmt = (comment ?? '').trim();
// if(cmt.length === 0) {
// throw new BadRequestException('A comment is required');
// }
// return cmt;
// };
// //rounding $ to 2 decimals
// export function roundMoney2(num: number): number {
// return Math.round((num + Number.EPSILON) * 100)/ 100;
// };
// export function computeMileageAmount(km: number, modifier: number): number {
// if(km < 0) throw new BadRequestException('mileage must be positive');
// if(modifier < 0) throw new BadRequestException('modifier must be positive');
// return roundMoney2(km * modifier);
// };
// //compat. types with Prisma.Decimal. work around Prisma import in utils.
// export type DecimalLike =
// | number
// | string
// | { toNumber?: () => number }
// | { toString?: () => string };
// //safe conversion to number
// export function toNumberSafe(value: DecimalLike): number {
// if(typeof value === 'number') return value;
// if(value && typeof (value as any).toNumber === 'function') return (value as any).toNumber();
// return Number(
// typeof (value as any)?.toString === 'function'
// ? (value as any).toString()
// : value,
// );
// }
// export const parseAttachmentId = (value: unknown): number | null => {
// if (value == null) {
// return null;
// }
// if (typeof value === 'number') {
// if (!Number.isInteger(value) || value <= 0) {
// throw new BadRequestException('Invalid attachment id');
// }
// return value;
// }
// if (typeof value === 'string') {
// const trimmed = value.trim();
// if (!trimmed.length) return null;
// if (!/^\d+$/.test(trimmed)) throw new BadRequestException('Invalid attachment id');
// const parsed = Number(trimmed);
// if (parsed <= 0) throw new BadRequestException('Invalid attachment id');
// return parsed;
// }
// throw new BadRequestException('Invalid attachment id');
// };
// //map of a row for DayExpenseResponse
// export function mapDbExpenseToDayResponse(row: {
// date: Date;
// amount: Prisma.Decimal | number | string | null;
// mileage?: Prisma.Decimal | number | string | null;
// comment: string;
// is_approved: boolean;
// bank_code?: { type?: string | null } | null;
// }): ExpenseResponse {
// const yyyyMmDd = row.date.toISOString().slice(0,10);
// const toNum = (value: any)=> (value == null ? 0 : Number(value));
// return {
// date: yyyyMmDd,
// type: normalizeType(row.bank_code?.type ?? 'UNKNOWN'),
// amount: toNum(row.amount),
// comment: row.comment,
// is_approved: row.is_approved,
// ...(row.mileage !== null ? { mileage: toNum(row.mileage) }: {}),
// };
// }
// export const computeAmountDecimal = (
// type: string,
// payload: {
// amount?: number;
// mileage?: number;
// },
// modifier: number,
// ): Prisma.Decimal => {
// if(type === 'MILEAGE') {
// const km = payload.mileage ?? 0;
// const amountNumber = computeMileageAmount(km, modifier);
// return new Prisma.Decimal(amountNumber);
// }
// return new Prisma.Decimal(payload.amount!);
// };

View File

@ -1,10 +0,0 @@
// import { Type } from "class-transformer";
// import { IsInt, Min, Max } from "class-validator";
// export class GetShiftsOverviewDto {
// @Type(()=> Number)
// @IsInt()
// @Min(1)
// @Max(26)
// period_id: number;
// }

View File

@ -1,23 +0,0 @@
// import { Prisma } from "@prisma/client";
// //custom prisma select to avoid employee_id exposure
// export const leaveRequestsSelect = {
// id: true,
// bank_code_id: true,
// leave_type: true,
// date: true,
// payable_hours: true,
// requested_hours: true,
// comment: true,
// approval_status: true,
// employee: { select: {
// id: true,
// user: { select: {
// email: true,
// first_name: true,
// last_name: true,
// }},
// }},
// } satisfies Prisma.LeaveRequestsSelect;
// export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;

View File

@ -1,126 +0,0 @@
// import { Injectable } from "@nestjs/common";
// import { Weekday, Prisma } from "@prisma/client";
// import { DATE_ISO_FORMAT, WEEKDAY } from "src/common/utils/constants.utils";
// import { PrismaService } from "src/prisma/prisma.service";
// import { ApplyResult } from "src/time-and-attendance/utils/type.utils";
// import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
// import { Result } from "src/common/errors/result-error.factory";
// @Injectable()
// export class SchedulePresetsApplyService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly emailResolver: EmailToIdResolver
// ) { }
// async applyToTimesheet(email: string, id: number, start_date_iso: string): Promise<Result<ApplyResult, string>> {
// if (!DATE_ISO_FORMAT.test(start_date_iso)) return { success: false, error: 'INVALID_PRESET' };
// const employee_id = await this.emailResolver.findIdByEmail(email);
// if (!employee_id.success) return { success: false, error: employee_id.error }
// const preset = await this.prisma.schedulePresets.findFirst({
// where: { id },
// include: {
// shifts: {
// orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }],
// select: {
// id: true,
// week_day: true,
// sort_order: true,
// start_time: true,
// end_time: true,
// is_remote: true,
// bank_code_id: true,
// },
// },
// },
// });
// if (!preset) return { success: false, error: `PRESET_NOT_FOUND` };
// const start_date = new Date(`${start_date_iso}T00:00:00.000Z`);
// const timesheet = await this.prisma.timesheets.upsert({
// where: { employee_id_start_date: { employee_id: employee_id.data, start_date: start_date } },
// update: {},
// create: { employee_id: employee_id.data, start_date: start_date },
// select: { id: true },
// });
// //index shifts by weekday
// const index_by_day = new Map<Weekday, typeof preset.shifts>();
// for (const shift of preset.shifts) {
// const list = index_by_day.get(shift.week_day) ?? [];
// list.push(shift);
// index_by_day.set(shift.week_day, list);
// }
// const addDays = (date: Date, days: number) =>
// new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
// const overlaps = (aStart: Date, aEnd: Date, bStart: Date, bEnd: Date) =>
// aStart.getTime() < bEnd.getTime() && aEnd.getTime() > bStart.getTime();
// let created = 0;
// let skipped = 0;
// await this.prisma.$transaction(async (tx) => {
// for (let i = 0; i < 7; i++) {
// const date = addDays(start_date, i);
// const week_day = WEEKDAY[date.getUTCDay()];
// const shifts = index_by_day.get(week_day) ?? [];
// if (shifts.length === 0) continue;
// const existing = await tx.shifts.findMany({
// where: { timesheet_id: timesheet.id, date: date },
// orderBy: { start_time: 'asc' },
// select: {
// start_time: true,
// end_time: true,
// bank_code_id: true,
// is_remote: true,
// comment: true,
// },
// });
// const payload: Prisma.ShiftsCreateManyInput[] = [];
// for (const shift of shifts) {
// if (shift.end_time.getTime() <= shift.start_time.getTime()) {
// return {
// success: false,
// error: `INVALID_PRESET_SHIFT`
// };
// }
// const conflict = existing.find((existe) => overlaps(
// shift.start_time, shift.end_time,
// existe.start_time, existe.end_time,
// ));
// if (conflict)
// return {
// success: false,
// error: `OVERLAPING_SHIFT`
// };
// payload.push({
// timesheet_id: timesheet.id,
// date: date,
// start_time: shift.start_time,
// end_time: shift.end_time,
// is_remote: shift.is_remote,
// comment: null,
// bank_code_id: shift.bank_code_id,
// });
// }
// if (payload.length) {
// const response = await tx.shifts.createMany({ data: payload, skipDuplicates: true });
// created += response.count;
// skipped += payload.length - response.count;
// }
// }
// });
// return { success: true, data: { timesheet_id: timesheet.id, created, skipped } };
// }
// }

View File

@ -1,26 +0,0 @@
// import { Type } from "class-transformer";
// import { IsDateString, IsInt, IsOptional, IsString } from "class-validator";
// export class SearchExpensesDto {
// @IsOptional()
// @Type(()=> Number)
// @IsInt()
// timesheet_id?: number;
// @IsOptional()
// @Type(()=> Number)
// @IsInt()
// bank_code_id?: number;
// @IsOptional()
// @IsString()
// comment_contains?: string;
// @IsOptional()
// @IsDateString()
// start_date: string;
// @IsOptional()
// @IsDateString()
// end_date: string;
// }

View File

@ -1,20 +0,0 @@
// import { Type } from "class-transformer";
// import { IsBoolean, IsInt, IsOptional } from "class-validator";
// export class SearchTimesheetDto {
// @IsOptional()
// @Type(() => Number)
// @IsInt()
// timesheet_id?: number;
// @IsOptional()
// @Type(()=> Number)
// @IsInt()
// employee_id?: number;
// @IsOptional()
// @Type(()=> Boolean)
// @IsBoolean()
// is_approved?: boolean;
// }

View File

@ -1,194 +0,0 @@
// import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
// import { DayShiftResponse } from "../types-and-interfaces/shifts-upsert.types";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { Prisma, Shifts } from "@prisma/client";
// import { UpsertShiftDto } from "../dtos/upsert-shift.dto";
// import { BaseApprovalService } from "src/common/shared/base-approval.service";
// import { PrismaService } from "src/prisma/prisma.service";
// import { toDateOnly } from "../helpers/shifts-date-time-helpers";
// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
// import { ShiftsHelpersService } from "../helpers/shifts.helpers";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// @Injectable()
// export class ShiftsCommandService extends BaseApprovalService<Shifts> {
// private readonly logger = new Logger(ShiftsCommandService.name);
// constructor(
// prisma: PrismaService,
// private readonly emailResolver: EmailToIdResolver,
// private readonly typeResolver: BankCodesResolver,
// private readonly helpersService: ShiftsHelpersService,
// ) { super(prisma); }
// //_____________________________________________________________________________________________
// // APPROVAL AND DELEGATE METHODS
// //_____________________________________________________________________________________________
// protected get delegate() {
// return this.prisma.shifts;
// }
// protected delegateFor(transaction: Prisma.TransactionClient) {
// return transaction.shifts;
// }
// async updateApproval(id: number, is_approved: boolean): Promise<Shifts> {
// return this.prisma.$transaction((transaction) =>
// this.updateApprovalWithTransaction(transaction, id, is_approved),
// );
// }
// //TODO: modifier le Master Crud pour recevoir l'ensemble des shifts de la pay-period et trier sur l'action 'create'| 'update' | 'delete'
// //_____________________________________________________________________________________________
// // MASTER CRUD METHOD
// //_____________________________________________________________________________________________
// async upsertShifts(
// email: string,
// action: UpsertAction,
// dto: UpsertShiftDto,
// ): Promise<{
// action: UpsertAction;
// day: DayShiftResponse[];
// }> {
// if (!dto.old_shift && !dto.new_shift) throw new BadRequestException('At least one of old or new shift must be provided');
// const date = dto.new_shift?.date ?? dto.old_shift?.date;
// if (!date) throw new BadRequestException("A date (YYYY-MM-DD) must be provided in old_shift or new_shift");
// if (dto.old_shift?.date && dto.new_shift?.date && dto.old_shift.date !== dto.new_shift.date) {
// throw new BadRequestException('old_shift.date and new_shift.date must be identical');
// }
// const employee_id = await this.emailResolver.findIdByEmail(email);//resolve employee id using email
// if(action === 'create') {
// if(!dto.new_shift || dto.old_shift) {
// throw new BadRequestException(`Only new_shift must be provided for create`);
// }
// return this.createShift(employee_id, date, dto);
// }
// if(action === 'update'){
// if(!dto.old_shift || !dto.new_shift) {
// throw new BadRequestException(`Both new_shift and old_shift must be provided for update`);
// }
// return this.updateShift(employee_id, date, dto);
// }
// throw new BadRequestException(`Unknown action: ${action}`);
// }
// //_________________________________________________________________
// // CREATE
// //_________________________________________________________________
// private async createShift(
// employee_id: number,
// date_iso: string,
// dto: UpsertShiftDto,
// ): Promise<{action: UpsertAction; day: DayShiftResponse[]}> {
// return this.prisma.$transaction(async (tx) => {
// const date_only = toDateOnly(date_iso);
// const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
// if(!timesheet) throw new NotFoundException('Timesheet not found')
// const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift);
// const new_bank_code_id = await this.helpersService.resolveBankIdRequired(tx, new_norm_shift.type, 'new_shift');
// const day_shifts = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
// await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift);
// await tx.shifts.create({
// data: {
// timesheet_id: timesheet.id,
// date: date_only,
// start_time: new_norm_shift.start_time,
// end_time: new_norm_shift.end_time,
// is_remote: new_norm_shift.is_remote,
// is_approved: new_norm_shift.is_approved,
// comment: new_norm_shift.comment ?? null,
// bank_code_id: new_bank_code_id,
// },
// });
// await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
// const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
// return { action: 'create', day: await this.helpersService.mapDay(fresh_shift)};
// });
// }
// //_________________________________________________________________
// // UPDATE
// //_________________________________________________________________
// private async updateShift(
// employee_id: number,
// date_iso: string,
// dto: UpsertShiftDto,
// ): Promise<{ action: UpsertAction; day: DayShiftResponse[];}>{
// return this.prisma.$transaction(async (tx) => {
// const date_only = toDateOnly(date_iso);
// const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
// if(!timesheet) throw new NotFoundException('Timesheet not found')
// const old_norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
// const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift, 'new_shift');
// const old_bank_code = await this.typeResolver.findByType(old_norm_shift.type);
// const new_bank_code = await this.typeResolver.findByType(new_norm_shift.type);
// const day_shifts = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
// const existing = await this.helpersService.findExactOldShift(tx, {
// timesheet_id: timesheet.id,
// date_only,
// norm: old_norm_shift,
// bank_code_id: old_bank_code.id,
// });
// if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
// await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift, existing.id);
// await tx.shifts.update({
// where: { id: existing.id },
// data: {
// start_time: new_norm_shift.start_time,
// end_time: new_norm_shift.end_time,
// is_remote: new_norm_shift.is_remote,
// comment: new_norm_shift.comment ?? null,
// bank_code_id: new_bank_code.id,
// },
// });
// await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
// const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
// return { action: 'update', day: await this.helpersService.mapDay(fresh_shift)};
// });
// }
// //_________________________________________________________________
// // DELETE
// //_________________________________________________________________
// async deleteShift(
// email: string,
// date_iso: string,
// dto: UpsertShiftDto,
// ){
// return this.prisma.$transaction(async (tx) => {
// const date_only = toDateOnly(date_iso); //converts to Date format
// const employee_id = await this.emailResolver.findIdByEmail(email);
// const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
// if(!timesheet) throw new NotFoundException('Timesheet not found')
// const norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
// const bank_code_id = await this.typeResolver.findByType(norm_shift.type);
// const existing = await this.helpersService.findExactOldShift(tx, {
// timesheet_id: timesheet.id,
// date_only,
// norm: norm_shift,
// bank_code_id: bank_code_id.id,
// });
// if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
// await tx.shifts.delete({ where: { id: existing.id } });
// await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
// });
// }
// }

View File

@ -1,10 +0,0 @@
// export interface OverviewRow {
// full_name: string;
// supervisor: string;
// total_regular_hrs: number;
// total_evening_hrs: number;
// total_overtime_hrs: number;
// total_expenses: number;
// total_mileage: number;
// is_approved: boolean;
// }

View File

@ -1,114 +0,0 @@
// import { Injectable, NotFoundException } from "@nestjs/common";
// import { PrismaService } from "src/prisma/prisma.service";
// import { NotificationsService } from "src/modules/notifications/services/notifications.service";
// import { computeHours } from "src/common/utils/date-utils";
// import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
// // const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 12);
// @Injectable()
// export class ShiftsQueryService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly notifs: NotificationsService,
// ) {}
// async getSummary(period_id: number): Promise<OverviewRow[]> {
// //fetch pay-period to display
// const period = await this.prisma.payPeriods.findFirst({
// where: { pay_period_no: period_id },
// });
// if(!period) {
// throw new NotFoundException(`pay-period ${period_id} not found`);
// }
// const { period_start, period_end } = period;
// //prepare shifts and expenses for display
// const shifts = await this.prisma.shifts.findMany({
// where: { date: { gte: period_start, lte: period_end } },
// include: {
// bank_code: true,
// timesheet: { include: {
// employee: { include: {
// user:true,
// supervisor: { include: { user: true } },
// } },
// } },
// },
// });
// const expenses = await this.prisma.expenses.findMany({
// where: { date: { gte: period_start, lte: period_end } },
// include: {
// bank_code: true,
// timesheet: { include: { employee: {
// include: { user:true,
// supervisor: { include: { user:true } },
// } },
// } },
// },
// });
// const mapRow = new Map<string, OverviewRow>();
// for(const shift of shifts) {
// const employeeId = shift.timesheet.employee.user_id;
// const user = shift.timesheet.employee.user;
// const sup = shift.timesheet.employee.supervisor?.user;
// let row = mapRow.get(employeeId);
// if(!row) {
// row = {
// full_name: `${user.first_name} ${user.last_name}`,
// supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
// total_regular_hrs: 0,
// total_evening_hrs: 0,
// total_overtime_hrs: 0,
// total_expenses: 0,
// total_mileage: 0,
// is_approved: false,
// };
// }
// const hours = computeHours(shift.start_time, shift.end_time);
// switch(shift.bank_code.type) {
// case 'regular' : row.total_regular_hrs += hours;
// break;
// case 'evening' : row.total_evening_hrs += hours;
// break;
// case 'overtime' : row.total_overtime_hrs += hours;
// break;
// default: row.total_regular_hrs += hours;
// }
// mapRow.set(employeeId, row);
// }
// for(const exp of expenses) {
// const employee_id = exp.timesheet.employee.user_id;
// const user = exp.timesheet.employee.user;
// const sup = exp.timesheet.employee.supervisor?.user;
// let row = mapRow.get(employee_id);
// if(!row) {
// row = {
// full_name: `${user.first_name} ${user.last_name}`,
// supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
// total_regular_hrs: 0,
// total_evening_hrs: 0,
// total_overtime_hrs: 0,
// total_expenses: 0,
// total_mileage: 0,
// is_approved: false,
// };
// }
// const amount = Number(exp.amount);
// row.total_expenses += amount;
// if(exp.bank_code.type === 'mileage') {
// row.total_mileage += amount;
// }
// mapRow.set(employee_id, row);
// }
// //return by default the list of employee in ascending alphabetical order
// return Array.from(mapRow.values()).sort((a,b) => a.full_name.localeCompare(b.full_name));
// }
// }

View File

@ -1,17 +0,0 @@
// export type DayShiftResponse = {
// start_time: string;
// end_time: string;
// type: string;
// is_remote: boolean;
// comment: string | null;
// }
// export type ShiftPayload = {
// date: string;
// start_time: string;
// end_time: string;
// type: string;
// is_remote: boolean;
// is_approved: boolean;
// comment?: string | null;
// }

View File

@ -1,87 +0,0 @@
// import { Body, Controller, Delete, Get, Header, Param, ParseBoolPipe, ParseIntPipe, Patch, Put, Query, } from "@nestjs/common";
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
// import { Roles as RoleEnum } from '.prisma/client';
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
// import { ShiftsCommandService } from "../services/shifts-command.service";
// import { ShiftsQueryService } from "../services/shifts-query.service";
// import { GetShiftsOverviewDto } from "../dtos/get-shift-overview.dto";
// import { ShiftPayloadDto, UpsertShiftDto } from "../dtos/upsert-shift.dto";
// import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
// @ApiTags('Shifts')
// @ApiBearerAuth('access-token')
// // @UseGuards()
// @Controller('shifts')
// export class ShiftsController {
// constructor(
// private readonly shiftsService: ShiftsQueryService,
// private readonly shiftsCommandService: ShiftsCommandService,
// ){}
// @Put('upsert/:email')
// async upsert_by_date(
// @Param('email') email_param: string,
// @Query('action') action: UpsertAction,
// @Body() payload: UpsertShiftDto,
// ) {
// return this.shiftsCommandService.upsertShifts(email_param, action, payload);
// }
// @Delete('delete/:email/:date')
// async remove(
// @Param('email') email: string,
// @Param('date') date: string,
// @Body() payload: UpsertShiftDto,
// ) {
// return this.shiftsCommandService.deleteShift(email, date, payload);
// }
// @Patch('approval/:id')
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
// async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
// return this.shiftsCommandService.updateApproval(id, isApproved);
// }
// @Get('summary')
// async getSummary( @Query() query: GetShiftsOverviewDto): Promise<OverviewRow[]> {
// return this.shiftsService.getSummary(query.period_id);
// }
// @Get('export.csv')
// @Header('Content-Type', 'text/csv; charset=utf-8')
// @Header('Content-Disposition', 'attachment; filename="shifts-validation.csv"')
// async exportCsv(@Query() query: GetShiftsOverviewDto): Promise<Buffer>{
// const rows = await this.shiftsService.getSummary(query.period_id);
// //CSV Headers
// const header = [
// 'full_name',
// 'supervisor',
// 'total_regular_hrs',
// 'total_evening_hrs',
// 'total_overtime_hrs',
// 'total_expenses',
// 'total_mileage',
// 'is_validated'
// ].join(',') + '\n';
// //CSV rows
// const body = rows.map(r => {
// const esc = (str: string) => `"${str.replace(/"/g, '""')}"`;
// return [
// esc(r.full_name),
// esc(r.supervisor),
// r.total_regular_hrs.toFixed(2),
// r.total_evening_hrs.toFixed(2),
// r.total_overtime_hrs.toFixed(2),
// r.total_expenses.toFixed(2),
// r.total_mileage.toFixed(2),
// r.is_approved,
// ].join(',');
// }).join('\n');
// return Buffer.from('\uFEFF' + header + body, 'utf8');
// }
// }

View File

@ -1,103 +0,0 @@
// import { BadRequestException, UnprocessableEntityException, NotFoundException, ConflictException } from "@nestjs/common";
// import { Prisma, Shifts } from "@prisma/client";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { OvertimeService } from "src/modules/business-logics/services/overtime.service";
// export type Tx = Prisma.TransactionClient;
// export type Normalized = Awaited<ReturnType<typeof normalizeShiftPayload>>;
// export class ShiftsHelpersService {
// constructor(
// private readonly bankTypeResolver: BankCodesResolver,
// private readonly overtimeService: OvertimeService,
// ) { }
// async ensureTimesheet(tx: Tx, employee_id: number, date_only: Date) {
// const start_of_week = weekStartSunday(date_only);
// return tx.timesheets.findUnique({
// where: { employee_id_start_date: { employee_id, start_date: start_of_week } },
// select: { id: true },
// });
// }
// async normalizeRequired(
// raw: UpsertShiftDto['new_shift'] | UpsertShiftDto['old_shift'] | undefined | null,
// label: 'old_shift' | 'new_shift' = 'new_shift',
// ): Promise<Normalized> {
// if (!raw) throw new BadRequestException(`${label} is required`);
// const norm = await normalizeShiftPayload(raw);
// if (norm.end_time.getTime() <= norm.start_time.getTime()) {
// throw new UnprocessableEntityException(` ${label}.end_time must be > ${label}.start_time`);
// }
// return norm;
// }
// async resolveBankIdRequired(tx: Tx, type: string, label: 'old_shift' | 'new_shift'): Promise<number> {
// const found = await this.bankTypeResolver.findByType(type, tx);
// const id = found?.id;
// if (typeof id !== 'number') {
// throw new NotFoundException(`bank code not found for ${label}.type: ${type ?? ''}`);
// }
// return id;
// }
// async getDayShifts(tx: Tx, timesheet_id: number, date_only: Date) {
// return tx.shifts.findMany({
// where: { timesheet_id, date: date_only },
// include: { bank_code: true },
// orderBy: { start_time: 'asc' },
// });
// }
// async findExactOldShift(
// tx: Tx,
// params: {
// timesheet_id: number;
// date_only: Date;
// norm: Normalized;
// bank_code_id: number;
// comment?: string;
// },
// ) {
// const { timesheet_id, date_only, norm, bank_code_id } = params;
// return tx.shifts.findFirst({
// where: {
// timesheet_id,
// date: date_only,
// start_time: norm.start_time,
// end_time: norm.end_time,
// is_remote: norm.is_remote,
// is_approved: norm.is_approved,
// comment: norm.comment ?? null,
// bank_code_id,
// },
// select: { id: true },
// });
// }
// async afterWriteOvertimeAndLog(tx: Tx, employee_id: number, date_only: Date) {
// // Switch regular → weekly overtime si > 40h
// await this.overtimeService.transformRegularHoursToWeeklyOvertime(employee_id, date_only, tx);
// const daily = await this.overtimeService.getDailyOvertimeHours(employee_id, date_only);
// const weekly = await this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only);
// // const [daily, weekly] = await Promise.all([
// // this.overtimeService.getDailyOvertimeHoursForDay(employee_id, date_only),
// // this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only),
// // ]);
// return { daily, weekly };
// }
// async mapDay(
// fresh: Array<Shifts & { bank_code: { type: string } | null }>,
// ): Promise<DayShiftResponse[]> {
// return fresh.map((s) => ({
// start_time: toStringFromHHmm(s.start_time),
// end_time: toStringFromHHmm(s.end_time),
// type: s.bank_code?.type ?? 'UNKNOWN',
// is_remote: s.is_remote,
// comment: s.comment ?? null,
// }));
// }
// }

View File

@ -1,58 +0,0 @@
// import { NotFoundException } from "@nestjs/common";
// export function overlaps(
// a_start_ms: number,
// a_end_ms: number,
// b_start_ms: number,
// b_end_ms: number,
// ): boolean {
// return a_start_ms < b_end_ms && b_start_ms < a_end_ms;
// }
// export function resolveBankCodeByType(type: string): Promise<number> {
// const bank = this.prisma.bankCodes.findFirst({
// where: { type },
// select: { id: true },
// });
// if (!bank) {
// throw new NotFoundException({ error_code: 'SHIFT_TYPE_UNKNOWN', message: `unknown shift type: ${type}` });
// }
// return bank.id;
// }
// export function normalizeShiftPayload(payload: {
// date: string,
// start_time: string,
// end_time: string,
// type: string,
// is_remote: boolean,
// is_approved: boolean,
// comment?: string | null,
// }) {
// //normalize shift's infos
// const date = payload.date?.trim();
// const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date ?? '');
// if (!m) throw new Error(`Invalid date format (expected YYYY-MM-DD): "${payload.date}"`);
// const year = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
// const asLocalDateOn = (input: string): Date => {
// // HH:mm ?
// const hm = /^(\d{2}):(\d{2})$/.exec((input ?? '').trim());
// if (hm) return new Date(year, mo - 1, d, Number(hm[1]), Number(hm[2]), 0, 0);
// const iso = new Date(input);
// if (isNaN(iso.getTime())) throw new Error(`Invalid time: "${input}"`);
// return new Date(year, mo - 1, d, iso.getHours(), iso.getMinutes(), iso.getSeconds(), iso.getMilliseconds());
// };
// const start_time = asLocalDateOn(payload.start_time);
// const end_time = asLocalDateOn(payload.end_time);
// const type = (payload.type || '').trim().toUpperCase();
// const is_remote = payload.is_remote;
// const is_approved = payload.is_approved;
// //normalize comment
// const trimmed = typeof payload.comment === 'string' ? payload.comment.trim() : null;
// const comment = trimmed && trimmed.length > 0 ? trimmed : null;
// return { date, start_time, end_time, type, is_remote, is_approved, comment };
// }

View File

@ -1,75 +0,0 @@
// export class TimesheetDto {
// start_day: string;
// end_day: string;
// label: string;
// shifts: ShiftDto[];
// expenses: ExpenseDto[]
// is_approved: boolean;
// }
// export class ShiftDto {
// date: string;
// type: string;
// start_time: string;
// end_time : string;
// comment: string;
// is_approved: boolean;
// is_remote: boolean;
// }
// export class ExpenseDto {
// type: string;
// amount: number;
// mileage: number;
// comment: string;
// is_approved: boolean;
// supervisor_comment: string;
// }
// export type DayShiftsDto = ShiftDto[];
// export class DetailedShifts {
// shifts: DayShiftsDto;
// regular_hours: number;
// evening_hours: number;
// overtime_hours: number;
// emergency_hours: number;
// comment: string;
// short_date: string;
// break_durations?: number;
// }
// export class DayExpensesDto {
// expenses: ExpenseDto[] = [];
// total_mileage: number;
// total_expense: number;
// }
// export class WeekDto {
// is_approved: boolean;
// shifts: {
// sun: DetailedShifts;
// mon: DetailedShifts;
// tue: DetailedShifts;
// wed: DetailedShifts;
// thu: DetailedShifts;
// fri: DetailedShifts;
// sat: DetailedShifts;
// }
// expenses: {
// sun: DayExpensesDto;
// mon: DayExpensesDto;
// tue: DayExpensesDto;
// wed: DayExpensesDto;
// thu: DayExpensesDto;
// fri: DayExpensesDto;
// sat: DayExpensesDto;
// }
// }
// export class TimesheetPeriodDto {
// weeks: WeekDto[];
// employee_full_name: string;
// }

View File

@ -1,67 +0,0 @@
// import { MS_PER_DAY } from "src/modules/shared/constants/date-time.constant";
// import { DAY_KEYS, DayKey } from "./timesheet.types";
// export function toUTCDateOnly(date: Date | string): Date {
// const d = new Date(date);
// return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
// }
// export function addDays(date:Date, days: number): Date {
// return new Date(date.getTime() + days * MS_PER_DAY);
// }
// export function endOfDayUTC(date: Date | string): Date {
// const d = toUTCDateOnly(date);
// return new Date(d.getTime() + MS_PER_DAY - 1);
// }
// export function isBetweenUTC(date: Date, start: Date, end_inclusive: Date): boolean {
// const time = date.getTime();
// return time >= start.getTime() && time <= end_inclusive.getTime();
// }
// export function toTimeString(date: Date): string {
// const hours = String(date.getUTCHours()).padStart(2,'0');
// const minutes = String(date.getUTCMinutes()).padStart(2,'0');
// return `${hours}:${minutes}`;
// }
// export function round2(num: number) {
// return Math.round(num * 100) / 100;
// }
// export function shortDate(date:Date): string {
// const mm = String(date.getUTCMonth()+1).padStart(2,'0');
// const dd = String(date.getUTCDate()).padStart(2,'0');
// return `${mm}/${dd}`;
// }
// export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
// const index = useUTC ? date.getUTCDay() : date.getDay(); // 0=Sunday..6=Saturday
// return DAY_KEYS[index];
// }
// export const toHHmm = (date: Date) => date.toISOString().slice(11, 16);
// export function parseISODate(iso: string): Date {
// const [ y, m, d ] = iso.split('-').map(Number);
// return new Date(y, (m ?? 1) - 1, d ?? 1);
// }
// export function parseHHmm(t: string): Date {
// const [ hh, mm ] = t.split(':').map(Number);
// return new Date(1970, 0, 1, hh || 0, mm || 0, 0, 0);
// }
// export const toNum = (value: any) =>
// value && typeof value.toNumber === 'function' ? value.toNumber() :
// typeof value === 'number' ? value :
// value ? Number(value) : 0;
// export const upper = (s?: string | null) => String(s ?? '').toUpperCase();
// export const toRangeFromPeriod = (period: { period_start: Date; period_end: Date }) => ({
// from: toUTCDateOnly(period.period_start),
// to: endOfDayUTC(period.period_end),
// });

View File

@ -1,111 +0,0 @@
// import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "../dtos/timesheet-period.dto";
// import { ShiftRow, ExpenseRow, ExpensesAmount, TimesheetMap } from "./timesheet.types";
// import { addDays, shortDate, toNum, upper } from "./timesheet.helpers";
// import { Prisma } from "@prisma/client";
// //mappers
// export const mapShiftRow = (shift: {
// date: Date;
// start_time: Date;
// end_time: Date;
// comment?: string | null;
// is_approved: boolean;
// is_remote: boolean;
// bank_code: { type: string };
// }): ShiftRow => ({
// date: shift.date,
// start_time: shift.start_time,
// end_time: shift.end_time,
// comment: shift.comment ?? '',
// is_approved: shift.is_approved,
// is_remote: shift.is_remote,
// type: upper(shift.bank_code.type),
// });
// export const mapExpenseRow = (expense: {
// date: Date;
// amount: Prisma.Decimal | number | null;
// mileage: Prisma.Decimal | number | null;
// comment?: string | null;
// is_approved: boolean;
// supervisor_comment?: string|null;
// bank_code: { type: string },
// }): ExpenseRow => ({
// date: expense.date,
// amount: toNum(expense.amount),
// mileage: toNum(expense.mileage),
// comment: expense.comment ?? '',
// is_approved: expense.is_approved,
// supervisor_comment: expense.supervisor_comment ?? '',
// type: upper(expense.bank_code.type),
// });
// // Factories
// export function makeEmptyDayExpenses(): DayExpensesDto {
// return {
// expenses: [],
// total_expense: -1,
// total_mileage: -1,
// };
// }
// export function makeEmptyWeek(week_start: Date): WeekDto {
// const make_empty_shifts = (offset: number): DetailedShifts => ({
// shifts: [],
// regular_hours: 0,
// evening_hours: 0,
// emergency_hours: 0,
// overtime_hours: 0,
// comment: '',
// short_date: shortDate(addDays(week_start, offset)),
// break_durations: 0,
// });
// return {
// is_approved: true,
// shifts: {
// sun: make_empty_shifts(0),
// mon: make_empty_shifts(1),
// tue: make_empty_shifts(2),
// wed: make_empty_shifts(3),
// thu: make_empty_shifts(4),
// fri: make_empty_shifts(5),
// sat: make_empty_shifts(6),
// },
// expenses: {
// sun: makeEmptyDayExpenses(),
// mon: makeEmptyDayExpenses(),
// tue: makeEmptyDayExpenses(),
// wed: makeEmptyDayExpenses(),
// thu: makeEmptyDayExpenses(),
// fri: makeEmptyDayExpenses(),
// sat: makeEmptyDayExpenses(),
// },
// };
// }
// export function makeEmptyPeriod(): TimesheetPeriodDto {
// return { weeks: [makeEmptyWeek(new Date()), makeEmptyWeek(new Date())], employee_full_name: '' };
// }
// export const makeAmounts = (): ExpensesAmount => ({
// expense: 0,
// mileage: 0,
// });
// export function makeEmptyTimesheet(params: {
// start_day: string;
// end_day: string;
// label: string;
// is_approved?: boolean;
// }): TimesheetMap {
// const { start_day, end_day, label, is_approved = false } = params;
// return {
// start_day,
// end_day,
// label,
// shifts: [],
// expenses: [],
// is_approved,
// };
// }

View File

@ -1,46 +0,0 @@
// import { EXPENSE_ASC_ORDER, EXPENSE_SELECT } from "../../../shared/selects/expenses.select";
// import { Injectable, NotFoundException } from "@nestjs/common";
// import { SHIFT_ASC_ORDER, SHIFT_SELECT } from "../../../shared/selects/shifts.select";
// import { PAY_PERIOD_SELECT } from "../../../shared/selects/pay-periods.select";
// import { PrismaService } from "src/prisma/prisma.service";
// @Injectable()
// export class TimesheetSelectorsService {
// constructor(readonly prisma: PrismaService){}
// async getPayPeriod(pay_year: number, pay_period_no: number) {
// const period = await this.prisma.payPeriods.findFirst({
// where: { pay_year, pay_period_no },
// select: PAY_PERIOD_SELECT ,
// });
// if(!period) throw new NotFoundException(`period ${pay_year}-${pay_period_no} not found`);
// return period;
// }
// async getShifts(employee_id: number, from: Date, to: Date) {
// return this.prisma.shifts.findMany({
// where: {timesheet: { is: { employee_id } }, date: { gte: from, lte: to } },
// select: SHIFT_SELECT,
// orderBy: SHIFT_ASC_ORDER,
// });
// }
// async getExpenses(employee_id: number, from: Date, to: Date) {
// return this.prisma.expenses.findMany({
// where: { timesheet: {is: { employee_id } }, date: { gte: from, lte: to } },
// select: EXPENSE_SELECT,
// orderBy: EXPENSE_ASC_ORDER,
// });
// }
// async getTimesheetWithShiftsAndExpenses(employee_id: number, start_date_week: Date) {
// return this.prisma.timesheets.findUnique({
// where: { employee_id_start_date: { employee_id, start_date: start_date_week } },
// select: {
// is_approved: true,
// shift: { select: SHIFT_SELECT, orderBy: SHIFT_ASC_ORDER },
// expense: { select: EXPENSE_SELECT, orderBy: EXPENSE_ASC_ORDER },
// },
// });
// }
// }

View File

@ -1,74 +0,0 @@
// export type ShiftRow = {
// date: Date;
// start_time: Date;
// end_time: Date;
// comment: string;
// is_approved?: boolean;
// is_remote: boolean;
// type: string
// };
// export type ExpenseRow = {
// date: Date;
// amount: number;
// mileage?: number | null;
// comment: string;
// type: string;
// is_approved?: boolean;
// supervisor_comment: string;
// };
// export type TimesheetMap = {
// start_day: string;
// end_day: string;
// label: string;
// shifts: ShiftRow[];
// expenses: ExpenseRow[]
// is_approved: boolean;
// }
// // Types
// export const SHIFT_TYPES = {
// REGULAR: 'REGULAR',
// EVENING: 'EVENING',
// OVERTIME: 'OVERTIME',
// EMERGENCY: 'EMERGENCY',
// HOLIDAY: 'HOLIDAY',
// VACATION: 'VACATION',
// SICK: 'SICK',
// } as const;
// export const EXPENSE_TYPES = {
// MILEAGE: 'MILEAGE',
// EXPENSE: 'EXPENSES',
// PER_DIEM: 'PER_DIEM',
// ON_CALL: 'ON_CALL',
// } as const;
// //makes the strings indexes for arrays
// export const DAY_KEYS = ['sun','mon','tue','wed','thu','fri','sat'] as const;
// export type DayKey = typeof DAY_KEYS[number];
// //shifts's hour by type
// export type ShiftsHours = {
// regular: number;
// evening: number;
// overtime: number;
// emergency: number;
// sick: number;
// vacation: number;
// holiday: number;
// };
// export const make_hours = (): ShiftsHours => ({
// regular: 0,
// evening: 0,
// overtime: 0,
// emergency: 0,
// sick: 0,
// vacation: 0,
// holiday: 0,
// });
// export type ExpensesAmount = {
// expense: number;
// mileage: number;
// };

View File

@ -1,171 +0,0 @@
// import {
// DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow,
// SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
// } from "./timesheet.types";
// import {
// isBetweenUTC, dayKeyFromDate, toTimeString, round2,
// toUTCDateOnly, endOfDayUTC, addDays
// } from "./timesheet.helpers";
// import { WeekDto, ShiftDto, TimesheetPeriodDto, DayExpensesDto, ExpenseDto } from "../dtos/timesheet-period.dto";
// import { getWeekStart, getWeekEnd, formatDateISO } from "src/common/utils/date-utils";
// import { makeAmounts, makeEmptyWeek } from "./timesheet.mappers";
// import { toDateString } from "src/modules/pay-periods/utils/pay-year.util";
// import { MS_PER_HOUR } from "src/modules/shared/constants/date-time.constant";
// export function computeWeekRange(week_offset = 0){
// //sets current week Sunday -> Saturday
// const base = new Date();
// const offset = new Date(base);
// offset.setDate(offset.getDate() + (week_offset * 7));
// const start = getWeekStart(offset, 0);
// const end = getWeekEnd(start);
// const start_day = formatDateISO(start);
// const end_day = formatDateISO(end);
// const label = `${(start_day)}.${(end_day)}`;
// return { start, end, start_day, end_day, label }
// };
// export function buildWeek(
// week_start: Date,
// week_end: Date,
// shifts: ShiftRow[],
// expenses: ExpenseRow[],
// ): WeekDto {
// const week = makeEmptyWeek(week_start);
// let all_approved = true;
// const day_times: Record<DayKey, Array<{ start: Date; end: Date }>> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = []; return acc;
// }, {} as Record<DayKey, Array<{ start: Date; end: Date}>>);
// const day_hours: Record<DayKey, ShiftsHours> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = make_hours(); return acc;
// }, {} as Record<DayKey, ShiftsHours>);
// const day_amounts: Record<DayKey, ExpensesAmount> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = makeAmounts(); return acc;
// }, {} as Record<DayKey, ExpensesAmount>);
// const day_expense_rows: Record<DayKey, DayExpensesDto> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = {
// expenses: [{
// type: '',
// amount: -1,
// mileage: -1,
// comment: '',
// is_approved: false,
// supervisor_comment: '',
// }],
// total_expense: -1,
// total_mileage: -1,
// };
// return acc;
// }, {} as Record<DayKey, DayExpensesDto>);
// //regroup hours per type of shifts
// const week_shifts = shifts.filter(shift => isBetweenUTC(shift.date, week_start, week_end));
// for (const shift of week_shifts) {
// const key = dayKeyFromDate(shift.date, true);
// week.shifts[key].shifts.push({
// date: toDateString(shift.date),
// type: shift.type,
// start_time: toTimeString(shift.start_time),
// end_time: toTimeString(shift.end_time),
// comment: shift.comment,
// is_approved: shift.is_approved ?? true,
// is_remote: shift.is_remote,
// } as ShiftDto);
// day_times[key].push({ start: shift.start_time, end: shift.end_time});
// const duration = Math.max(0, (shift.end_time.getTime() - shift.start_time.getTime())/ MS_PER_HOUR);
// const type = (shift.type || '').toUpperCase();
// if ( type === SHIFT_TYPES.REGULAR) day_hours[key].regular += duration;
// else if( type === SHIFT_TYPES.EVENING) day_hours[key].evening += duration;
// else if( type === SHIFT_TYPES.EMERGENCY) day_hours[key].emergency += duration;
// else if( type === SHIFT_TYPES.OVERTIME) day_hours[key].overtime += duration;
// else if( type === SHIFT_TYPES.SICK) day_hours[key].sick += duration;
// else if( type === SHIFT_TYPES.VACATION) day_hours[key].vacation += duration;
// else if( type === SHIFT_TYPES.HOLIDAY) day_hours[key].holiday += duration;
// all_approved = all_approved && (shift.is_approved ?? true );
// }
// //regroupe amounts to type of expenses
// const week_expenses = expenses.filter(expense => isBetweenUTC(expense.date, week_start, week_end));
// for (const expense of week_expenses) {
// const key = dayKeyFromDate(expense.date, true);
// const type = (expense.type || '').toUpperCase();
// const row: ExpenseDto = {
// type,
// amount: round2(expense.amount ?? 0),
// mileage: round2(expense.mileage ?? 0),
// comment: expense.comment ?? '',
// is_approved: expense.is_approved ?? true,
// supervisor_comment: expense.supervisor_comment ?? '',
// };
// day_expense_rows[key].expenses.push(row);
// if(type === EXPENSE_TYPES.MILEAGE) {
// day_amounts[key].mileage += row.mileage ?? 0;
// } else {
// day_amounts[key].expense += row.amount;
// }
// all_approved = all_approved && row.is_approved;
// }
// for (const key of DAY_KEYS) {
// //return exposed dto data
// week.shifts[key].regular_hours = round2(day_hours[key].regular);
// week.shifts[key].evening_hours = round2(day_hours[key].evening);
// week.shifts[key].overtime_hours = round2(day_hours[key].overtime);
// week.shifts[key].emergency_hours = round2(day_hours[key].emergency);
// //calculate gaps between shifts
// const times = day_times[key].sort((a,b) => a.start.getTime() - b.start.getTime());
// let gaps = 0;
// for (let i = 1; i < times.length; i++) {
// const gap = (times[i].start.getTime() - times[i - 1].end.getTime()) / MS_PER_HOUR;
// if(gap > 0) gaps += gap;
// }
// week.shifts[key].break_durations = round2(gaps);
// //daily totals
// const totals = day_amounts[key];
// day_expense_rows[key].total_mileage = round2(totals.mileage);
// day_expense_rows[key].total_expense = round2(totals.expense);
// }
// week.is_approved = all_approved;
// return week;
// }
// export function buildPeriod(
// period_start: Date,
// period_end: Date,
// shifts: ShiftRow[],
// expenses: ExpenseRow[],
// employeeFullName = ''
// ): TimesheetPeriodDto {
// const week1_start = toUTCDateOnly(period_start);
// const week1_end = endOfDayUTC(addDays(week1_start, 6));
// const week2_start = toUTCDateOnly(addDays(week1_start, 7));
// const week2_end = endOfDayUTC(period_end);
// const weeks: WeekDto[] = [
// buildWeek(week1_start, week1_end, shifts, expenses),
// buildWeek(week2_start, week2_end, shifts, expenses),
// ];
// return {
// weeks,
// employee_full_name: employeeFullName,
// };
// }

View File

@ -1,137 +0,0 @@
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
// import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
// import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
// import { parseISODate, parseHHmm } from "./utils-helpers-others/timesheet.helpers";
// import { TimesheetsQueryService } from "./timesheets-query.service";
// import { BaseApprovalService } from "src/common/shared/base-approval.service";
// import { Prisma, Timesheets } from "@prisma/client";
// import { CreateTimesheetDto } from "./create-timesheet.dto";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { PrismaService } from "src/prisma/prisma.service";
// import { TimesheetMap } from "./utils-helpers-others/timesheet.types";
// import { Shift, Expense } from "../dtos/timesheet.dto";
// @Injectable()
// export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
// constructor(
// prisma: PrismaService,
// private readonly query: TimesheetsQueryService,
// private readonly emailResolver: EmailToIdResolver,
// private readonly timesheetResolver: EmployeeTimesheetResolver,
// private readonly bankTypeResolver: BankCodesResolver,
// ) {super(prisma);}
// //_____________________________________________________________________________________________
// // APPROVAL AND DELEGATE METHODS
// //_____________________________________________________________________________________________
// protected get delegate() {
// return this.prisma.timesheets;
// }
// protected delegateFor(transaction: Prisma.TransactionClient) {
// return transaction.timesheets;
// }
// async updateApproval(id: number, isApproved: boolean): Promise<Timesheets> {
// return this.prisma.$transaction((transaction) =>
// this.updateApprovalWithTransaction(transaction, id, isApproved),
// );
// }
// async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise<Timesheets> {
// const timesheet = await this.updateApprovalWithTransaction(transaction, timesheetId, isApproved);
// await transaction.shifts.updateMany({
// where: { timesheet_id: timesheetId },
// data: { is_approved: isApproved },
// });
// await transaction.expenses.updateManyAndReturn({
// where: { timesheet_id: timesheetId },
// data: { is_approved: isApproved },
// });
// return timesheet;
// }
// /**_____________________________________________________________________________________________
// create/update/delete shifts and expenses from 1 or many timesheet(s)
// -this function receives an email and an array of timesheets
// -this function will find the timesheets with all shifts and expenses
// -this function will calculate total hours, total expenses, filtered by types,
// cumulate in daily and weekly.
// -the timesheet_id will be determined using the employee email
// -with the timesheet_id, all shifts and expenses will be fetched
// -with shift_id and expense_id, this function will compare both
// datas from the DB and from the body of the function and then:
// -it will create a shift if no shift is found in the DB
// -it will update a shift if a shift is found in the DB
// -it will delete a shift if a shift is found and no data is received from the frontend
// This function will be used for the Timesheet Page for an employee to enter, modify or delete and entry
// This function will also be used in the modal of the timesheet validation page to
// allow a supervisor to enter, modify or delete and entry of a selected employee
// _____________________________________________________________________________________________*/
// async findTimesheetsByEmailAndPayPeriod(email: string, year: number, period_no: number, timesheets: Timesheets[]): Promise<Timesheets[]> {
// const employee_id = await this.emailResolver.findIdByEmail(email);
// return timesheets;
// }
// async upsertOrDeleteShiftsByEmailAndDate(email:string, shift_ids: Shift[]) {}
// async upsertOrDeleteExpensesByEmailAndDate(email:string, expenses_id: Expense[]) {}
// //_____________________________________________________________________________________________
// //
// //_____________________________________________________________________________________________
// async createWeekShiftsAndReturnOverview(
// email:string,
// shifts: CreateTimesheetDto[],
// week_offset = 0,
// ): Promise<TimesheetMap> {
// //fetchs employee matchint user's email
// const employee_id = await this.emailResolver.findIdByEmail(email);
// if(!employee_id) throw new NotFoundException(`employee for ${ email } not found`);
// //insure that the week starts on sunday and finishes on saturday
// const base = new Date();
// base.setDate(base.getDate() + week_offset * 7);
// const start_week = getWeekStart(base, 0);
// const end_week = getWeekEnd(start_week);
// const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, base)
// if(!timesheet) throw new NotFoundException(`no timesheet found for employe ${employee_id}`);
// //validations and insertions
// for(const shift of shifts) {
// const date = parseISODate(shift.date);
// if (date < start_week || date > end_week) throw new BadRequestException(`date ${shift.date} not in current week`);
// const bank_code = await this.bankTypeResolver.findByType(shift.type)
// if(!bank_code) throw new BadRequestException(`Invalid bank_code type: ${shift.type}`);
// await this.prisma.shifts.create({
// data: {
// timesheet_id: timesheet.id,
// bank_code_id: bank_code.id,
// date: date,
// start_time: parseHHmm(shift.start_time),
// end_time: parseHHmm(shift.end_time),
// comment: shift.comment ?? null,
// is_approved: false,
// is_remote: false,
// },
// });
// }
// return this.query.getTimesheetByEmail(email, week_offset);
// }
// }

View File

@ -1,54 +0,0 @@
// import { makeEmptyTimesheet, mapExpenseRow, mapShiftRow } from './utils-helpers-others/timesheet.mappers';
// import { buildPeriod, computeWeekRange } from './utils-helpers-others/timesheet.utils';
// import { TimesheetSelectorsService } from './utils-helpers-others/timesheet.selectors';
// import { TimesheetPeriodDto } from './timesheet-period.dto';
// import { toRangeFromPeriod } from './utils-helpers-others/timesheet.helpers';
// import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
// import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
// import { PrismaService } from 'src/prisma/prisma.service';
// import { TimesheetMap } from './utils-helpers-others/timesheet.types';
// import { Injectable } from '@nestjs/common';
// @Injectable()
// export class TimesheetsQueryService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly emailResolver: EmailToIdResolver,
// private readonly fullNameResolver: FullNameResolver,
// private readonly selectors: TimesheetSelectorsService,
// ) {}
// async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
// const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
// const full_name = await this.fullNameResolver.resolveFullName(employee_id); //finds the employee full name using employee_id
// const period = await this.selectors.getPayPeriod(year, period_no);//finds the pay period using year and period_no
// const{ from, to } = toRangeFromPeriod(period); //finds start and end dates
// //finds all shifts from selected period
// const [raw_shifts, raw_expenses] = await Promise.all([
// this.selectors.getShifts(employee_id, from, to),
// this.selectors.getExpenses(employee_id, from, to),
// ]);
// // data mapping
// const shifts = raw_shifts.map(mapShiftRow);
// const expenses = raw_expenses.map(mapExpenseRow);
// return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
// }
// async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetMap> {
// const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
// const { start, start_day, end_day, label } = computeWeekRange(week_offset);
// const timesheet = await this.selectors.getTimesheetWithShiftsAndExpenses(employee_id, start); //fetch timesheet shifts and expenses
// if(!timesheet) return makeEmptyTimesheet({ start_day, end_day, label});
// //maps all shifts of selected timesheet
// const shifts = timesheet.shift.map(mapShiftRow);
// const expenses = timesheet.expense.map(mapExpenseRow);
// return { start_day, end_day, label, shifts, expenses, is_approved: timesheet.is_approved};
// }
// }

View File

@ -1,51 +0,0 @@
// import { BadRequestException, Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
// import { TimesheetsQueryService } from './timesheets-query.service';
// import { CreateWeekShiftsDto } from './create-timesheet.dto';
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
// import { Roles as RoleEnum } from '.prisma/client';
// import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
// import { TimesheetsCommandService } from './timesheets-command.service';
// import { TimesheetPeriodDto } from './timesheet-period.dto';
// import { TimesheetMap } from './timesheet.types';
// @ApiTags('Timesheets')
// @ApiBearerAuth('access-token')
// // @UseGuards()
// @Controller('timesheets')
// export class TimesheetsController {
// constructor(
// private readonly timesheetsQuery: TimesheetsQueryService,
// private readonly timesheetsCommand: TimesheetsCommandService,
// ) {}
// @Get()
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// async getPeriodByQuery(
// @Query('year', ParseIntPipe ) year: number,
// @Query('period_no', ParseIntPipe ) period_no: number,
// @Query('email') email?: string
// ): Promise<TimesheetPeriodDto> {
// if(!email || !(email = email.trim())) throw new BadRequestException('Query param "email" is mandatory for this route.');
// return this.timesheetsQuery.findAll(year, period_no, email);
// }
// @Get('/:email')
// async getByEmail(
// @Param('email') email: string,
// @Query('offset') offset?: string,
// ): Promise<TimesheetMap> {
// const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
// return this.timesheetsQuery.getTimesheetByEmail(email, week_offset);
// }
// @Post('shifts/:email')
// async createTimesheetShifts(
// @Param('email') email: string,
// @Body() dto: CreateWeekShiftsDto,
// @Query('offset') offset?: string,
// ): Promise<TimesheetMap> {
// const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
// return this.timesheetsCommand.createWeekShiftsAndReturnOverview(email, dto.shifts, week_offset);
// }
// }

View File

@ -1,4 +0,0 @@
// import { PartialType } from "@nestjs/swagger";
// import { CreateExpenseDto } from "./create-expense.dto";
// export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {}

View File

@ -1,59 +0,0 @@
// import { Transform, Type } from "class-transformer";
// import {
// IsNumber,
// IsOptional,
// IsString,
// Matches,
// MaxLength,
// Min,
// ValidateIf,
// ValidateNested
// } from "class-validator";
// export class ExpensePayloadDto {
// @IsString()
// type!: string;
// @ValidateIf(o => (o.type ?? '').toUpperCase() !== 'MILEAGE')
// @IsNumber()
// @Min(0)
// amount?: number;
// @ValidateIf(o => (o.type ?? '').toUpperCase() === 'MILEAGE')
// @IsNumber()
// @Min(0)
// mileage?: number;
// @IsString()
// @MaxLength(280)
// @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
// comment!: string;
// @IsOptional()
// @Transform(({ value }) => {
// if (value === null || value === undefined || value === '') return undefined;
// if (typeof value === 'number') return value.toString();
// if (typeof value === 'string') {
// const trimmed = value.trim();
// return trimmed.length ? trimmed : undefined;
// }
// return undefined;
// })
// @IsString()
// @Matches(/^\d+$/)
// @MaxLength(255)
// attachment?: string;
// }
// export class UpsertExpenseDto {
// @IsOptional()
// @ValidateNested()
// @Type(()=> ExpensePayloadDto)
// old_expense?: ExpensePayloadDto;
// @IsOptional()
// @ValidateNested()
// @Type(()=> ExpensePayloadDto)
// new_expense?: ExpensePayloadDto;
// }

View File

@ -1,43 +0,0 @@
// import { Type } from "class-transformer";
// import { IsBoolean, IsOptional, IsString, Matches, MaxLength, ValidateNested } from "class-validator";
// export const COMMENT_MAX_LENGTH = 280;
// export class ShiftPayloadDto {
// @Matches(/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/)
// date!: string;
// @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
// start_time!: string;
// @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
// end_time!: string;
// @IsString()
// type!: string;
// @IsBoolean()
// is_remote!: boolean;
// @IsBoolean()
// is_approved!: boolean;
// @IsOptional()
// @IsString()
// @MaxLength(COMMENT_MAX_LENGTH)
// comment?: string;
// };
// export class UpsertShiftDto {
// @IsOptional()
// @ValidateNested()
// @Type(()=> ShiftPayloadDto)
// old_shift?: ShiftPayloadDto;
// @IsOptional()
// @ValidateNested()
// @Type(()=> ShiftPayloadDto)
// new_shift?: ShiftPayloadDto;
// };

View File

@ -1 +0,0 @@
// export const COMMENT_MAX_LENGTH = 280;