refactor(utils): moved utils to shared utils folder

This commit is contained in:
Matthieu Haineault 2025-10-27 11:47:38 -04:00
parent ac1eaabb5d
commit 5e49bc5df6
74 changed files with 259 additions and 282 deletions

View File

@ -1,20 +1,6 @@
{
"openapi": "3.0.0",
"paths": {
"/": {
"get": {
"operationId": "AppController_getHello",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"App"
]
}
},
"/health": {
"get": {
"operationId": "HealthController_check",
@ -319,16 +305,16 @@
]
}
},
"/preferences/{email}": {
"/preferences": {
"patch": {
"operationId": "PreferencesController_updatePreferences",
"parameters": [
{
"name": "email",
"name": "PreferencesDto",
"required": true,
"in": "path",
"in": "body",
"schema": {
"type": "string"
"$ref": "#/components/schemas/PreferencesDto"
}
}
],
@ -337,7 +323,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PreferencesDto"
"type": "number"
}
}
}

View File

@ -1,12 +1,4 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
export class AppController { }

View File

@ -1,8 +1,4 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
export class AppService { }

View File

@ -1,4 +1,4 @@
import { Body, Controller, Param, Patch } from "@nestjs/common";
import { Body, Controller, Patch } from "@nestjs/common";
import { PreferencesService } from "../services/preferences.service";
import { PreferencesDto } from "../dtos/preferences.dto";
@ -6,9 +6,12 @@ import { PreferencesDto } from "../dtos/preferences.dto";
export class PreferencesController {
constructor(private readonly service: PreferencesService){}
@Patch(':email')
async updatePreferences(@Param('email') email: string, @Body()payload: PreferencesDto) {
return this.service.updatePreferences(email, payload);
@Patch()
async updatePreferences(
@Body() user_id: number,
@Body() payload: PreferencesDto
) {
return this.service.updatePreferences(user_id, payload);
}
}

View File

@ -1,10 +1,8 @@
import { Module } from "@nestjs/common";
import { PreferencesController } from "./controllers/preferences.controller";
import { PreferencesService } from "./services/preferences.service";
import { SharedModule } from "../../time-and-attendance/modules/shared/shared.module";
import { Module } from "@nestjs/common";
@Module({
imports: [SharedModule],
controllers: [ PreferencesController ],
providers: [ PreferencesService ],
exports: [ PreferencesService ],

View File

@ -1,20 +1,15 @@
import { Injectable } from "@nestjs/common";
import { Preferences } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import { PreferencesDto } from "../dtos/preferences.dto";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { PrismaService } from "src/prisma/prisma.service";
import { Preferences } from "@prisma/client";
import { Injectable } from "@nestjs/common";
@Injectable()
export class PreferencesService {
constructor(
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver ,
){}
constructor( private readonly prisma: PrismaService ){}
async updatePreferences(email: string, dto: PreferencesDto ): Promise<Preferences> {
const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
async updatePreferences(user_id: number, dto: PreferencesDto ): Promise<Preferences> {
return this.prisma.preferences.update({
where: { user_id },
where: { id: user_id },
data: {
notifications: dto.notifications,
dark_mode: dto.dark_mode,

View File

@ -1,18 +1,15 @@
import { Module } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
//import { AfterHoursService } from "./services/after-hours.service";
import { HolidayService } from "./services/holiday.service";
import { OvertimeService } from "./services/overtime.service";
import { SickLeaveService } from "./services/sick-leave.service";
import { OvertimeService } from "./services/overtime.service";
import { VacationService } from "./services/vacation.service";
import { HolidayService } from "./services/holiday.service";
import { MileageService } from "./services/mileage.service";
import { PrismaService } from "src/prisma/prisma.service";
import { Module } from "@nestjs/common";
//AfterHours is not used, need to clarify infos before implementing into shifts.service
@Module({
providers: [
PrismaService,
//AfterHoursService,
HolidayService,
MileageService,
OvertimeService,
@ -20,7 +17,6 @@ import { MileageService } from "./services/mileage.service";
VacationService
],
exports: [
//AfterHoursService,
HolidayService,
MileageService,
OvertimeService,

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,16 +1,12 @@
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { ExpenseDto } from "../dtos/expense.dto";
import { Body, Controller, Delete, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import { CreateResult, ExpenseUpsertService, UpdateResult } from "../services/expense-upsert.service";
import { updateExpenseDto } from "src/time-and-attendance/modules/expenses/dtos/update-expense.dto";
import { updateExpenseDto } from "src/time-and-attendance/modules/expenses/dtos/expense-update.dto";
import { ExpenseDto } from "../dtos/expense-create.dto";
@Controller('expense')
export class ExpenseController {
constructor(
private readonly prisma: PrismaService,
private readonly upsert_service: ExpenseUpsertService,
){}
constructor( private readonly upsert_service: ExpenseUpsertService ){}
@Post(':timesheet_id')
create(

View File

@ -1,5 +1,5 @@
import { OmitType, PartialType } from "@nestjs/swagger";
import { ExpenseDto } from "./expense.dto";
import { ExpenseDto } from "./expense-create.dto";
export class updateExpenseDto extends PartialType (
OmitType(ExpenseDto, ['is_approved', 'timesheet_id'] as const)

View File

@ -2,14 +2,10 @@ import { ExpensesArchivalService } from "./services/expenses-archival.service";
import { ExpenseUpsertService } from "src/time-and-attendance/modules/expenses/services/expense-upsert.service";
import { ExpenseController } from "src/time-and-attendance/modules/expenses/controllers/expense.controller";
import { Module } from "@nestjs/common";
import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module";
import { SharedModule } from "src/time-and-attendance/modules/shared/shared.module";
@Module({
imports: [ BusinessLogicsModule, SharedModule ],
controllers: [ ExpenseController ],
providers: [ ExpenseUpsertService, ExpensesArchivalService ],
exports: [ ExpensesArchivalService ],
})
export class ExpensesModule {}

View File

@ -1,5 +0,0 @@
export const toDateFromString = (ymd: string): Date => {
return new Date(`${ymd}T00:00:00:000Z`);
}
export const toStringFromDate = (date: Date) =>
date.toISOString().slice(0,10);

View File

@ -1,9 +1,11 @@
import { toDateFromString, toStringFromDate } from "../helpers/expenses-date-time-helpers";
import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time-helpers";
import { Injectable, NotFoundException } from "@nestjs/common";
import { updateExpenseDto } from "../dtos/update-expense.dto";
import { GetExpenseDto } from "../dtos/get-expense.dto";
import { updateExpenseDto } from "../dtos/expense-update.dto";
import { GetExpenseDto } from "../dtos/expense-get.dto";
import { PrismaService } from "src/prisma/prisma.service";
import { ExpenseDto } from "../dtos/expense.dto";
import { ExpenseDto } from "../dtos/expense-create.dto";
import { Prisma } from "@prisma/client";
type Normalized = { date: Date; comment: string; supervisor_comment?: string; };
@ -43,18 +45,7 @@ export class ExpenseUpsertService {
is_approved: dto.is_approved,
},
//return the newly created expense with id
select: {
id: true,
timesheet_id: true,
bank_code_id: true,
attachment: true,
date: true,
amount: true,
mileage: true,
comment: true,
supervisor_comment: true,
is_approved: true,
},
select: expense_select,
});
//build an object to return to the frontend to display
@ -103,18 +94,7 @@ export class ExpenseUpsertService {
const expense = await this.prisma.expenses.update({
where: { id },
data,
select: {
id: true,
timesheet_id: true,
bank_code_id: true,
attachment: true,
date: true,
amount: true,
mileage: true,
comment: true,
supervisor_comment: true,
is_approved: true,
},
select: expense_select,
});
const updated: GetExpenseDto = {
@ -181,4 +161,21 @@ export class ExpenseUpsertService {
if (Number.isNaN(parsed)) throw new Error(`Invalid value : ${value} for ${field}`);
return parsed;
};
}
}
export const expense_select = {
id: true,
timesheet_id: true,
bank_code_id: true,
attachment: true,
date: true,
amount: true,
mileage: true,
comment: true,
supervisor_comment: true,
is_approved: true,
} satisfies Prisma.ExpensesSelect;

View File

@ -1,18 +1,17 @@
import { Module } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module";
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service";
import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service";
import { VacationService } from "src/time-and-attendance/domains/services/vacation.service";
import { LeaveRequestController } from "src/time-and-attendance/modules/leave-requests/controllers/leave-requests.controller";
import { LeaveRequestsService } from "src/time-and-attendance/modules/leave-requests/services/leave-request.service";
import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module";
import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util";
import { SharedModule } from "src/time-and-attendance/modules/shared/shared.module";
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 { ShiftsModule } from "src/time-and-attendance/modules/time-tracker/shifts/shifts.module";
import { Module } from "@nestjs/common";
@Module({
imports: [BusinessLogicsModule, ShiftsModule, SharedModule],
imports: [BusinessLogicsModule, ShiftsModule ],
controllers: [LeaveRequestController],
providers: [
VacationService,
@ -22,9 +21,6 @@ import { ShiftsModule } from "src/time-and-attendance/modules/time-tracker/shift
PrismaService,
LeaveRequestsUtils,
],
exports: [
LeaveRequestsService,
],
})
export class LeaveRequestsModule {}

View File

@ -1,6 +1,6 @@
import { Prisma } from "@prisma/client";
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
import { LeaveRequestRow } from "../utils/leave-requests.select";
import { LeaveRequestRow } from "src/time-and-attendance/utils/selects.utils";
const toNum = (value?: Prisma.Decimal | null) =>
value !== null && value !== undefined ? Number(value) : undefined;

View File

@ -1,15 +1,15 @@
import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common";
import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service";
import { LeaveRequestViewDto } from "src/time-and-attendance/modules/leave-requests/dtos/leave-request-view.dto";
import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/modules/leave-requests/dtos/upsert-leave-request.dto";
import { mapRowToView } from "src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper";
import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util";
import { leaveRequestsSelect } from "src/time-and-attendance/modules/leave-requests/utils/leave-requests.select";
import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client";
import { normalizeDates, toDateOnly } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers";
import { LeaveRequestViewDto } from "src/time-and-attendance/modules/leave-requests/dtos/leave-request-view.dto";
import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils";
import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util";
import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/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/modules/leave-requests/mappers/leave-requests.mapper";
@Injectable()
@ -26,8 +26,8 @@ export class HolidayLeaveRequestsService {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
const dates = normalizeDates(dto.dates);
if (!bank_code) throw new NotFoundException(`bank_code not found`);
if (!dates.length) throw new BadRequestException('Dates array must not be empty');
const created: LeaveRequestViewDto[] = [];

View File

@ -4,7 +4,6 @@ import { roundToQuarterHour } from "src/common/utils/date-utils";
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
import { mapRowToView } from "../mappers/leave-requests.mapper";
import { leaveRequestsSelect } from "../utils/leave-requests.select";
import { PrismaService } from "src/prisma/prisma.service";
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service";
import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service";
@ -13,14 +12,13 @@ import { normalizeDates, toDateOnly, toISODateKey } from "src/time-and-attendanc
import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util";
import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils";
@Injectable()
export class LeaveRequestsService {
constructor(
private readonly prisma: PrismaService,
private readonly holidayService: HolidayService,
private readonly sickLogic: SickLeaveService,
private readonly sickLeaveService: SickLeaveService,
private readonly vacationService: VacationService,
private readonly vacationLogic: VacationService,
private readonly leaveUtils: LeaveRequestsUtils,
private readonly emailResolver: EmailToIdResolver,

View File

@ -7,10 +7,10 @@ import { LeaveRequestViewDto } from "src/time-and-attendance/modules/leave-reque
import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/modules/leave-requests/dtos/upsert-leave-request.dto";
import { mapRowToView } from "src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper";
import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util";
import { leaveRequestsSelect } from "src/time-and-attendance/modules/leave-requests/utils/leave-requests.select";
import { normalizeDates, toDateOnly } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers";
import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils";
@Injectable()
@ -18,7 +18,6 @@ export class SickLeaveRequestsService {
constructor(
private readonly prisma: PrismaService,
private readonly sickService: SickLeaveService,
private readonly leaveUtils: LeaveRequestsUtils,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) {}

View File

@ -6,11 +6,10 @@ import { VacationService } from "src/time-and-attendance/domains/services/vacati
import { LeaveRequestViewDto } from "src/time-and-attendance/modules/leave-requests/dtos/leave-request-view.dto";
import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/modules/leave-requests/dtos/upsert-leave-request.dto";
import { mapRowToView } from "src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper";
import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util";
import { leaveRequestsSelect } from "src/time-and-attendance/modules/leave-requests/utils/leave-requests.select";
import { normalizeDates, toDateOnly } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers";
import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils";
@Injectable()
@ -18,7 +17,6 @@ export class VacationLeaveRequestsService {
constructor(
private readonly prisma: PrismaService,
private readonly vacationService: VacationService,
private readonly leaveUtils: LeaveRequestsUtils,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) {}

View File

@ -1,8 +1,8 @@
import { LeaveRequestRow } from 'src/time-and-attendance/utils/selects.utils';
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
import { mapArchiveRowToView } from '../mappers/leave-requests-archive.mapper';
import { mapRowToView } from '../mappers/leave-requests.mapper';
import { LeaveRequestArchiveRow } from './leave-requests-archive.select';
import { LeaveRequestRow } from './leave-requests.select';
/** Active (table leave_requests) : proxy to base mapper */
export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto {

View File

@ -2,15 +2,16 @@
import { BadRequestException, Injectable } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { LeaveTypes } from "@prisma/client";
import { toDateOnly, toStringFromDate, hhmmFromLocal } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers";
// import { toDateOnly, toStringFromDate } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers";
import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types";
import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service";
import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time-helpers";
// import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service";
@Injectable()
export class LeaveRequestsUtils {
constructor(
private readonly prisma: PrismaService,
private readonly shiftsService: ShiftsUpsertService,
// private readonly shiftsService: ShiftsUpsertService,
){}
async syncShift(
@ -27,7 +28,7 @@ export class LeaveRequestsUtils {
if (duration_minutes > 8 * 60) {
throw new BadRequestException("Amount of hours cannot exceed 8 hours per day.");
}
const date_only = toDateOnly(date);
const date_only = toDateFromString(date);
const yyyy_mm_dd = toStringFromDate(date_only);
@ -78,7 +79,7 @@ export class LeaveRequestsUtils {
iso_date: string,
type: LeaveTypes,
) {
const date_only = toDateOnly(iso_date);
const date_only = toDateFromString(iso_date);
const yyyy_mm_dd = toStringFromDate(date_only);
const existing = await this.prisma.shifts.findFirst({
where: {

View File

@ -1,19 +1,14 @@
import { PayPeriodsQueryService } from "./services/pay-periods-query.service";
import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module";
import { PayPeriodsController } from "./controllers/pay-periods.controller";
import { PrismaService } from "src/prisma/prisma.service";
import { PrismaModule } from "src/prisma/prisma.module";
import { SharedModule } from "../shared/shared.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, SharedModule, BusinessLogicsModule],
providers: [
PayPeriodsQueryService,
PrismaService,
],
controllers: [PayPeriodsController],
exports: [ PayPeriodsQueryService ],
})
export class PayperiodsModule {}

View File

@ -1,14 +1,10 @@
import { toStringFromDate, toUTCDate } from "src/time-and-attendance/utils/date-time-helpers";
export const ANCHOR_ISO = '2023-12-17'; // ancre date
const PERIOD_DAYS = 14;
const PERIODS_PER_YEAR = 26;
const MS_PER_DAY = 86_400_000;
const toUTCDate = (iso: string | Date) => {
const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso;
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
};
export const toDateString = (d: Date) => d.toISOString().slice(0, 10);
export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number {
const ANCHOR = toUTCDate(anchorISO);
const d = toUTCDate(date);
@ -27,10 +23,10 @@ export function computePeriod(pay_year: number, period_no: number, anchorISO = A
return {
period_no: period_no,
pay_year: pay_year,
payday: toDateString(pay),
period_start: toDateString(start),
period_end: toDateString(end),
label: `${toDateString(start)}.${toDateString(end)}`,
payday: toStringFromDate(pay),
period_start: toStringFromDate(start),
period_end: toStringFromDate(end),
label: `${toStringFromDate(start)}.${toStringFromDate(end)}`,
start, end,
};
}

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,2 +0,0 @@
const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/;
const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/;

View File

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

View File

@ -15,8 +15,8 @@ export const toDateOnly = (s: string): Date => {
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 toStringFromDate = (d: Date) =>
// `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
export const toISOtoDateOnly = (iso: string): Date => {

View File

@ -1,6 +1,6 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, PrismaClient } from "@prisma/client";
import { weekStartSunday } from "src/time-and-attendance/modules/time-tracker/shifts/helpers/shifts-date-time-helpers";
import { weekStartSunday } from "src/time-and-attendance/utils/date-time-helpers";
import { PrismaService } from "src/prisma/prisma.service";
import { EmailToIdResolver } from "./resolve-email-id.utils";

View File

@ -1,23 +1,12 @@
import { BadRequestException, Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from "@nestjs/common";
import { BadRequestException, Body, Controller, Delete, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import { CreateResult, ShiftsUpsertService, UpdateResult } from "../services/shifts-upsert.service";
import { updateShiftDto } from "../dtos/update-shift.dto";
import { ShiftDto } from "../dtos/shift.dto";
import { ShiftsGetService } from "../services/shifts-get.service";
import { updateShiftDto } from "../dtos/shift-update.dto";
import { ShiftDto } from "../dtos/shift-create.dto";
@Controller('shift')
export class ShiftController {
constructor(
private readonly upsert_service: ShiftsUpsertService,
private readonly get_service: ShiftsGetService
){}
// @Get()
// async getShiftsByIds(
// @Query("shift_ids") shift_ids: string) {
// const parsed = shift_ids.split(/,\s*/).map(value => Number(value)).filter(Number.isFinite);
// return this.get_service.getShiftByShiftId(parsed);
// }
constructor( private readonly upsert_service: ShiftsUpsertService ){}
@Post(':timesheet_id')
createBatch(
@ -25,7 +14,6 @@ export class ShiftController {
@Body()dtos: ShiftDto[]): Promise<CreateResult[]> {
const list = Array.isArray(dtos) ? dtos : [];
if(list.length === 0) throw new BadRequestException('Body is missing or invalid (create shifts)')
return this.upsert_service.createShifts(timesheet_id, dtos)
}

View File

@ -1,5 +1,5 @@
import { PartialType, OmitType } from "@nestjs/swagger";
import { ShiftDto } from "./shift.dto";
import { ShiftDto } from "./shift-create.dto";
export class updateShiftDto extends PartialType (
//allows update using ShiftDto and preventing OmitType variables to be modified

View File

@ -1,6 +1,19 @@
import { Injectable } from "@nestjs/common";
import { ShiftsArchive } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
/**
* _____________________________________________________________________________________
*
*
* This service is not used. Will be use to atrchive a list of shifts using a cron job.
*
*
* _____________________________________________________________________________________
*/
@Injectable()
export class ShiftsArchivalService {
constructor(private readonly prisma: PrismaService){}

View File

@ -1,7 +1,18 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { GetShiftDto } from "../dtos/get-shift.dto";
import { toStringFromDate, toStringFromHHmm } from "../helpers/shifts-date-time-helpers";
import { GetShiftDto } from "../dtos/shift-get.dto";
import { toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time-helpers";
/**
* _____________________________________________________________________________________
*
*
* This service is not used. Could be use to show a list of shifts.
*
* For the moment, the module Timesheets is used to display shifts, filtered by employee
*
* _____________________________________________________________________________________
*/
@Injectable()
export class ShiftsGetService {

View File

@ -1,10 +1,10 @@
import { toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../helpers/shifts-date-time-helpers";
import { toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time-helpers";
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { OvertimeService, WeekOvertimeSummary } from "src/time-and-attendance/domains/services/overtime.service";
import { updateShiftDto } from "../dtos/update-shift.dto";
import { updateShiftDto } from "../dtos/shift-update.dto";
import { PrismaService } from "src/prisma/prisma.service";
import { GetShiftDto } from "../dtos/get-shift.dto";
import { ShiftDto } from "../dtos/shift.dto";
import { GetShiftDto } from "../dtos/shift-get.dto";
import { ShiftDto } from "../dtos/shift-create.dto";
type Normalized = { date: Date; start_time: Date; end_time: Date; };

View File

@ -1,24 +1,13 @@
import { ShiftsArchivalService } from './services/shifts-archival.service';
import { BusinessLogicsModule } from 'src/time-and-attendance/domains/business-logics.module';
import { NotificationsModule } from '../../../../modules/notifications/notifications.module';
import { ShiftsUpsertService } from './services/shifts-upsert.service';
import { ShiftsGetService } from './services/shifts-get.service';
import { ShiftController } from './controllers/shift.controller';
import { SharedModule } from '../../shared/shared.module';
import { Module } from '@nestjs/common';
@Module({
imports: [
BusinessLogicsModule,
NotificationsModule,
SharedModule,
],
imports: [ BusinessLogicsModule ],
controllers: [ShiftController],
providers: [
ShiftsArchivalService,
ShiftsGetService,
ShiftsUpsertService,
],
exports: [ ShiftsUpsertService, ShiftsGetService ],
providers: [ ShiftsGetService, ShiftsUpsertService ],
exports: [ ShiftsUpsertService ],
})
export class ShiftsModule {}

View File

@ -1,3 +1,4 @@
//ensures the week starts from sunday
export function weekStartSunday(date_local: Date): Date {
const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate()));
const dow = start.getDay(); // 0 = dimanche
@ -28,4 +29,9 @@ export const toHHmmFromString = (hhmm: string): Date => {
//converts Date format to string
export const toDateFromString = (ymd: string): Date => {
return new Date(`${ymd}T00:00:00:000Z`);
}
}
export const toUTCDate = (iso: string | Date) => {
const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso;
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
};

View File

@ -1,6 +1,20 @@
import { Prisma } from "@prisma/client";
//custom prisma select to avoid employee_id exposure
export const expense_select = {
id: true,
timesheet_id: true,
bank_code_id: true,
attachment: true,
date: true,
amount: true,
mileage: true,
comment: true,
supervisor_comment: true,
is_approved: true,
} satisfies Prisma.ExpensesSelect;
export const leaveRequestsSelect = {
id: true,
bank_code_id: true,
@ -20,4 +34,4 @@ export const leaveRequestsSelect = {
}},
} satisfies Prisma.LeaveRequestsSelect;
export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;
export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;

View File

@ -0,0 +1,79 @@
// 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

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

View File

@ -0,0 +1,23 @@
// 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

@ -0,0 +1,2 @@
// const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/;
// const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/;

View File

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