reafactor(EsLint): Eslint corrections

This commit is contained in:
Matthieu Haineault 2026-02-27 13:19:38 -05:00
parent 368c5b1a2a
commit c427cc7718
37 changed files with 286 additions and 217 deletions

View File

@ -26,6 +26,7 @@ export default tseslint.config(
},
{
rules: {
"no-unused-vars": "off",
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn'

View File

@ -28,7 +28,9 @@ import { CustomerSupportModule } from 'src/customer-support/customer-support.mod
ChatbotModule,
CustomerSupportModule,
],
controllers: [AppController],
controllers: [
AppController
],
providers: [
AppService,
{

View File

@ -3,8 +3,11 @@ import { TicketController } from "src/customer-support/tickets/ticket.controller
import { TicketService } from "src/customer-support/tickets/ticket.service";
@Module({
imports: [],
controllers: [TicketController],
providers: [TicketService],
exports: [],
controllers: [
TicketController
],
providers: [
TicketService
],
}) export class CustomerSupportModule { }

View File

@ -3,7 +3,11 @@ import { TicketController } from "src/customer-support/tickets/ticket.controller
import { TicketService } from "src/customer-support/tickets/ticket.service";
@Module({
imports: [TicketService],
providers: [TicketController]
imports: [
TicketService
],
providers: [
TicketController
]
})
export class TicketModule { }

View File

@ -1,8 +1,3 @@
import 'reflect-metadata';
// import * as nodeCrypto from 'crypto';
// if (!(globalThis as any).crypto) {
// (globalThis as any).crypto = nodeCrypto;
// }
import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';
import { ModulesGuard } from './common/guards/modules.guard';
@ -21,7 +16,7 @@ async function bootstrap() {
const reflector = app.get(Reflector);
app.useGlobalGuards(
new ModulesGuard(reflector), //deny-by-default and Module-based Access Control
new ModulesGuard(reflector),
);
// Authentication and session
@ -46,19 +41,11 @@ async function bootstrap() {
// Enable CORS
app.enableCors({
origin: ['http://10.100.251.2:9011', 'http://10.5.14.111:9012', 'http://10.100.251.2:9013', 'http://localhost:9000', 'https://app.targo.ca', 'https://portail.targo.ca','https://staging.app.targo.ca'],
origin: ['http://10.100.251.2:9011', 'http://10.5.14.111:9012', 'http://10.100.251.2:9013', 'http://localhost:9000', 'https://app.targo.ca', 'https://portail.targo.ca', 'https://staging.app.targo.ca'],
credentials: true,
});
await app.listen(process.env.PORT ?? 3000);
// migration function calls
// await initializePaidTimeOff();
// await initializePreferences();
// await extractOldTimesheets();
// await extractOldShifts();
// await extractOldExpenses();
// await initSupervisor();
}
bootstrap();

View File

@ -0,0 +1,3 @@
export class BankCodeDto {
type:string;
}

View File

@ -6,7 +6,8 @@ import { applyHolidayRequalifications, applyOvertimeRequalifications, computeWee
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service";
import { select_csv_expense_lines, select_csv_shift_lines } from "src/time-and-attendance/utils/selects.utils";
import { BillableShiftType } from "src/time-and-attendance/shifts/shift.types";
import { BillableShiftType } from "src/time-and-attendance/shifts/shift.dto";
@Injectable()
export class CsvExportService {

View File

@ -8,7 +8,9 @@ import { PaidTimeOffController } from "src/time-and-attendance/paid-time-off/pai
import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service";
@Module({
controllers: [PaidTimeOffController],
controllers: [
PaidTimeOffController
],
providers: [
PrismaPostgresService,
EmailToIdResolver,

View File

@ -18,7 +18,9 @@ export class PaidTimeOffBankHoursService {
private readonly emailResolver: EmailToIdResolver,
) { }
getPaidTimeOffTotalsWithEmployeeEmail = async (email: string): Promise<Result<Partial<PaidTimeOffDto>, string>> => {
getPaidTimeOffTotalsWithEmployeeEmail = async (
email: string
): Promise<Result<Partial<PaidTimeOffDto>, string>> => {
const employee_info = await this.emailResolver.findIdByEmail(email);
@ -112,6 +114,7 @@ export class PaidTimeOffBankHoursService {
});
return { success: true, data: true };
} catch (error) {
console.error(error);
return { success: false, error: 'PAID_TIME_OFF_NOT_FOUND' };
}
};

View File

@ -2,25 +2,13 @@ import { Type } from "class-transformer";
import { IsArray, IsBoolean, IsEmail, IsInt, ValidateNested } from "class-validator";
export class BulkCrewApprovalItemDto {
@IsInt()
pay_year: number;
@IsInt()
period_no: number;
@IsEmail()
employee_email!: string;
@IsBoolean()
approve: boolean;
@IsInt() pay_year: number;
@IsInt() period_no: number;
@IsEmail() employee_email: string;
@IsBoolean() approve: boolean;
}
export class BulkCrewApprovalDto {
@IsBoolean()
include_subtree: boolean = false;
@IsArray()
@ValidateNested({each: true})
@Type(()=> BulkCrewApprovalItemDto)
items: BulkCrewApprovalItemDto[]
@IsBoolean() include_subtree: boolean = false;
@IsArray() @ValidateNested({ each: true }) @Type(() => BulkCrewApprovalItemDto) items: BulkCrewApprovalItemDto[]
}

View File

@ -27,7 +27,7 @@ export class EmployeePeriodOverviewDto {
holiday_hours: number;
vacation_hours: number;
};
weekly_hours: number[];
weekly_hours: number[];
total_hours: number;
expenses: number;
mileage: number;

View File

@ -26,7 +26,9 @@ export class PayPeriodsController {
@Get("date/:date")
@ModuleAccessAllowed(ModulesEnum.timesheets)
async findByDate(@Param("date") date: string) {
async findByDate(
@Param("date") date: string
) {
return this.queryService.findByDate(date);
}

View File

@ -1,9 +1,13 @@
import { PayPeriods } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PayPeriodDto } from "src/time-and-attendance/pay-period/dtos/overview-pay-period.dto";
const toDateString = (date: Date) => date.toISOString().slice(0, 10); // "YYYY-MM-DD"
const toDateString = (
date: Date
) => date.toISOString().slice(0, 10); // "YYYY-MM-DD"
export function mapPayPeriodToDto(row: PayPeriods): PayPeriodDto {
export function mapPayPeriodToDto(
row: PayPeriods
): PayPeriodDto {
const start = toDateString(row.period_start);
const end = toDateString(row.period_end);
const pay = toDateString(row.payday);
@ -12,12 +16,13 @@ export function mapPayPeriodToDto(row: PayPeriods): PayPeriodDto {
period_start: toDateString(row.period_start),
period_end: toDateString(row.period_end),
payday:pay,
// pay_year: new Date(pay).getFullYear(),
pay_year: row.pay_year,
label: `${start}.${end}`,
};
}
export function mapMany(rows: PayPeriods[]): PayPeriodDto[] {
export function mapMany(
rows: PayPeriods[]
): PayPeriodDto[] {
return rows.map(mapPayPeriodToDto);
}

View File

@ -8,8 +8,12 @@ import { GetOverviewService } from "src/time-and-attendance/pay-period/services/
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
@Module({
imports:[TimesheetsModule],
controllers: [PayPeriodsController],
imports: [
TimesheetsModule
],
controllers: [
PayPeriodsController
],
providers: [
PayPeriodsQueryService,
PayPeriodsCommandService,
@ -17,6 +21,4 @@ import { PayPeriodEventService } from "src/time-and-attendance/pay-period/servic
PayPeriodEventService,
EmailToIdResolver,
],
})
export class PayperiodsModule {}
}) export class PayperiodsModule { }

View File

@ -11,7 +11,10 @@ export class GetOverviewService {
private readonly prisma: PrismaPostgresService,
) { }
async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodOverviewDto, string>> {
async getOverviewByYearPeriod(
pay_year: number,
period_no: number
): Promise<Result<PayPeriodOverviewDto, string>> {
const period = computePeriod(pay_year, period_no);
const overview = await this.buildOverview({
period_start: period.period_start,
@ -26,7 +29,9 @@ export class GetOverviewService {
return { success: true, data: overview.data }
}
async buildOverview(overview: Overview): Promise<Result<PayPeriodOverviewDto, string>> {
async buildOverview(
overview: Overview
): Promise<Result<PayPeriodOverviewDto, string>> {
const employee_overviews = await this.prisma.employees.findMany({
where: {
OR: [
@ -103,7 +108,12 @@ export class GetOverviewService {
}
}
const ensure = (id: number, first_name: string, last_name: string, email: string) => {
const ensure = (
id: number,
first_name: string,
last_name: string,
email: string
) => {
if (!by_employee.has(id)) {
by_employee.set(id, this.createEmployeeSeeds(email, first_name, last_name));
}

View File

@ -14,7 +14,11 @@ export class PayPeriodsCommandService {
) { }
//function to approve pay-periods according to selected crew members
async bulkApproveEmployee(email: string, timesheet_ids: number[], is_approved: boolean): Promise<Result<{ shifts: number, expenses: number }, string>> {
async bulkApproveEmployee(
email: string,
timesheet_ids: number[],
is_approved: boolean
): Promise<Result<{ shifts: number, expenses: number }, string>> {
let shifts: Prisma.BatchPayload;
let expenses: Prisma.BatchPayload;
@ -56,7 +60,8 @@ export class PayPeriodsCommandService {
is_approved: is_approved,
}
})
} catch (_error) {
} catch (error) {
console.error(error);
return { success: false, error: 'UNKNOWN_ERROR_VALIDATING' }
}

View File

@ -10,7 +10,10 @@ export class PayPeriodsQueryService {
constructor(
private readonly prisma: PrismaPostgresService) { }
async findOneByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodDto, string>> {
async findOneByYearPeriod(
pay_year: number,
period_no: number
): Promise<Result<PayPeriodDto, string>> {
const row = await this.prisma.payPeriods.findFirst({
where: { pay_year, pay_period_no: period_no },
});
@ -32,7 +35,9 @@ export class PayPeriodsQueryService {
}
//function to cherry pick a Date to find a period
async findByDate(date: string): Promise<Result<PayPeriodDto, string>> {
async findByDate(
date: string
): Promise<Result<PayPeriodDto, string>> {
const dt = new Date(date);
const row = await this.prisma.payPeriods.findFirst({
where: { period_start: { lte: dt }, period_end: { gte: dt } },

View File

@ -1,11 +1,9 @@
import { Controller, Param, Body, Get, Post, Delete, Patch } from "@nestjs/common";
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
import { SchedulePresetUpdateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update.service";
import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-delete.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Access } from "src/common/decorators/module-access.decorators";
@ -29,21 +27,25 @@ export class SchedulePresetsController {
@Post('create')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async createPreset(@Body() dto: SchedulePresetsDto) {
async createPreset(
@Body() dto: SchedulePresetsDto
) {
return await this.createService.createPreset(dto);
}
@Patch('update')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async updatePreset(
@Body() dto: SchedulePresetsDto) {
@Body() dto: SchedulePresetsDto
) {
return await this.updateService.updatePreset(dto);
}
@Delete('delete/:id')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async deletePreset(
@Param('id') id: number) {
@Param('id') id: number
) {
return await this.deleteService.deletePreset(id);
}

View File

@ -1,5 +1,5 @@
import { Weekday } from "prisma/postgres/generated/prisma/client/postgres/client";
import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches} from "class-validator";
import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches } from "class-validator";
import { HH_MM_REGEX } from "src/common/utils/constants.utils";
export class SchedulePresetsDto {
@ -17,12 +17,12 @@ export class SchedulePresetShiftsDto {
@IsOptional() @IsBoolean() is_remote?: boolean;
}
export const WEEKDAY_MAP: Record<Weekday, number> = {
SUN: 0,
MON: 1,
TUE: 2,
WED: 3,
THU: 4,
FRI: 5,
SAT: 6,
};
export const WEEKDAY_MAP: Weekday[] = [
'SUN',
'MON',
'TUE',
'WED',
'THU',
'FRI',
'SAT'
]

View File

@ -18,7 +18,9 @@ import { PayPeriodEventService } from "../pay-period/services/pay-period-event.s
@Module({
controllers: [SchedulePresetsController],
controllers: [
SchedulePresetsController
],
providers: [
SchedulePresetsGetService,
SchedulePresetsCreateService,

View File

@ -1,17 +1,15 @@
import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { sevenDaysFrom, toDateFromString, toStringFromDate, toStringFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Result } from "src/common/errors/result-error.factory";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
import { WEEKDAY_MAP } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
import { $Enums, SchedulePresetShifts } from "prisma/postgres/generated/prisma/client/postgres/client";
import { SchedulePresetShifts } from "prisma/postgres/generated/prisma/client/postgres/client";
@Injectable()
@ -24,7 +22,11 @@ export class SchedulePresetsApplyService {
private readonly payPeriodEventService: PayPeriodEventService,
) { }
async applyPresetToTimesheet(email: string, timesheet_id: number, employee_email?: string): Promise<Result<boolean, string>> {
async applyPresetToTimesheet(
email: string,
timesheet_id: number,
employee_email?: string
): Promise<Result<boolean, string>> {
const user_email = employee_email ?? email;
const employee_id = await this.emailResolver.findIdByEmail(user_email);
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
@ -63,15 +65,15 @@ export class SchedulePresetsApplyService {
if (timesheet.is_approved) return { success: false, error: 'INVALID_TIMESHEET' };
if (timesheet.shift.length > 0) return { success: false, error: 'INVALID_TIMESHEET' };
const dated_map = await sevenDaysFrom(timesheet.start_date);
const dated_map = sevenDaysFrom(timesheet.start_date);
let created_shifts: ShiftDto[] = [];
const created_shifts: ShiftDto[] = [];
for (const preset_shift of default_preset_shifts) {
const date = dated_map.find(date => date.getUTCDay() === WEEKDAY_MAP[preset_shift.week_day])
if (!date) return { success: false, error: 'INVALID_PRESET_DATE' };
const shift = await this.createShiftFromPreset(preset_shift, date!, timesheet.id)
const shift = await this.createShiftFromPreset(preset_shift, date, timesheet.id)
if (!shift.success) return { success: false, error: shift.error };
created_shifts.push(shift.data);
@ -99,7 +101,7 @@ export class SchedulePresetsApplyService {
const user_email = employee_email ?? email;
const employee_id = await this.emailResolver.findIdByEmail(user_email);
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
const week_day = Object.keys(WEEKDAY_MAP)[week_day_index];
const week_day = WEEKDAY_MAP[week_day_index];
const preset_shift = await this.prisma.employees.findFirst({
where: { id: employee_id.data, },
@ -108,7 +110,7 @@ export class SchedulePresetsApplyService {
select: {
id: true,
shifts: {
where: { week_day: $Enums.Weekday[week_day] },
where: { week_day },
select: {
bank_code_id: true,
start_time: true,
@ -146,7 +148,11 @@ export class SchedulePresetsApplyService {
return { success: true, data: true };
}
private createShiftFromPreset = async (preset: Partial<SchedulePresetShifts>, date: Date, timesheet_id: number): Promise<Result<ShiftDto, string>> => {
private createShiftFromPreset = async (
preset: Partial<SchedulePresetShifts>,
date: Date,
timesheet_id: number
): Promise<Result<ShiftDto, string>> => {
const type = await this.typeResolver.findTypeByBankCodeId(preset.bank_code_id!);
if (!type.success) return { success: false, error: 'INVALID_PRESET_SHIFT' };

View File

@ -1,9 +1,6 @@
import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory";
@ -14,10 +11,10 @@ export class SchedulePresetsCreateService {
private readonly prisma: PrismaPostgresService,
private readonly typeResolver: BankCodesResolver,
) { }
//_________________________________________________________________
// CREATE
//_________________________________________________________________
async createPreset(dto: SchedulePresetsDto): Promise<Result<boolean, string>> {
async createPreset(
dto: SchedulePresetsDto
): Promise<Result<boolean, string>> {
try {
//validate new unique name
const existing = await this.prisma.schedulePresets.findFirst({
@ -80,6 +77,7 @@ export class SchedulePresetsCreateService {
});
return { success: true, data: true }
} catch (error) {
console.error(error);
return { success: false, error: 'INVALID_SCHEDULE_PRESET' }
}
}

View File

@ -6,24 +6,23 @@ import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
export class SchedulePresetDeleteService {
constructor(private readonly prisma: PrismaPostgresService) { }
//_________________________________________________________________
// DELETE
//_________________________________________________________________
async deletePreset(preset_id: number): Promise<Result<boolean, string>> {
async deletePreset(
preset_id: number
): Promise<Result<boolean, string>> {
const preset = await this.prisma.schedulePresets.findUnique({
where: { id: preset_id },
select: { id: true },
});
if (!preset) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
const updated_employees = await this.prisma.employees.updateMany({
await this.prisma.employees.updateMany({
where: { schedule_preset_id: preset_id },
data: {
schedule_preset_id: 0,
},
});
await this.prisma.$transaction(async (tx) => {
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: preset_id } });
await tx.schedulePresets.delete({ where: { id: preset_id } });

View File

@ -1,9 +1,6 @@
import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { SchedulePresetsDto, SchedulePresetShiftsDto } from "../schedule-presets.dto";
import { Result } from "src/common/errors/result-error.factory";
@Injectable()
@ -40,6 +37,7 @@ export class SchedulePresetsGetService {
return { success: true, data: response };
} catch (error) {
console.error(error);
return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
}
}

View File

@ -1,8 +1,6 @@
import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory";
@ -14,10 +12,9 @@ export class SchedulePresetUpdateService {
private readonly typeResolver: BankCodesResolver,
) { }
//_________________________________________________________________
// UPDATE
//_________________________________________________________________
async updatePreset(dto: SchedulePresetsDto): Promise<Result<boolean, string>> {
async updatePreset(
dto: SchedulePresetsDto
): Promise<Result<boolean, string>> {
const existing = await this.prisma.schedulePresets.findFirst({
where: { id: dto.id },
select: {

View File

@ -25,10 +25,11 @@ export class ShiftsCreateService {
private readonly payPeriodEventService: PayPeriodEventService,
) { }
//_________________________________________________________________
// CREATE WRAPPER FUNCTION FOR ONE OR MANY INPUT
//_________________________________________________________________
async createOneOrManyShifts(email: string, shifts: ShiftDto[], is_from_timesheet: boolean = true): Promise<Result<boolean, string>> {
async createOneOrManyShifts(
email: string,
shifts: ShiftDto[],
is_from_timesheet: boolean = true
): Promise<Result<boolean, string>> {
try {
//verify if array is empty or not
if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'NO_DATA_RECEIVED' };
@ -71,13 +72,16 @@ export class ShiftsCreateService {
// returns array of created shifts
return { success: true, data: true }
} catch (error) {
return { success: false, error }
return { success: false, error: `${error}` }
}
}
//_________________________________________________________________
// CREATE
//_________________________________________________________________
async createShift(employee_id: number, dto: ShiftDto): Promise<Result<ShiftDto, string>> {
async createShift(
employee_id: number,
dto: ShiftDto
): Promise<Result<ShiftDto, string>> {
try {
//transform string format to date and HHmm
const normed_shift = await this.normalizeShiftDto(dto);
@ -99,9 +103,9 @@ export class ShiftsCreateService {
select: { id: true, date: true, start_time: true, end_time: true },
});
for (const existing of existing_shifts) {
const existing_start = await toDateFromString(existing.start_time);
const existing_end = await toDateFromString(existing.end_time);
const existing_date = await toDateFromString(existing.date);
const existing_start = toDateFromString(existing.start_time);
const existing_end = toDateFromString(existing.end_time);
const existing_date = toDateFromString(existing.date);
const has_overlap = overlaps(
{ start: normed_shift.data.start_time, end: normed_shift.data.end_time, date: normed_shift.data.date },
@ -183,13 +187,11 @@ export class ShiftsCreateService {
}
return { success: true, data: shift };
} catch (error) {
console.error(error);
return { success: false, error: `INVALID_SHIFT` };
}
}
//_________________________________________________________________
// LOCAL HELPERS
//_________________________________________________________________
//converts all string hours and date to Date and HHmm formats
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
@ -200,6 +202,14 @@ export class ShiftsCreateService {
const start_time = toDateFromHHmm(dto.start_time);
const end_time = toDateFromHHmm(dto.end_time);
return { success: true, data: { date, start_time, end_time, bank_code_id: bank_code_id.data } };
return {
success: true,
data: {
date,
start_time,
end_time,
bank_code_id: bank_code_id.data
}
};
}
}

View File

@ -13,13 +13,12 @@ export class ShiftsDeleteService {
private readonly emailResolver: EmailToIdResolver,
private readonly payPeriodEventService: PayPeriodEventService,
) { }
//_________________________________________________________________
// DELETE
//_________________________________________________________________
//finds shifts using shit_ids
//ajust paid-time-off banks
//blocs deletion if approved
async deleteShift(shift_id: number, email: string, is_from_timesheet: boolean = true): Promise<Result<number, string>> {
async deleteShift(
shift_id: number,
email: string,
is_from_timesheet: boolean = true
): Promise<Result<number, string>> {
try {
//verify if email is valid or not
@ -39,7 +38,7 @@ export class ShiftsDeleteService {
});
if (!shift || shift.timesheet.employee_id !== employee_id.data)
return { success: false, error: 'SHIFT_NOT_FOUND'}
return { success: false, error: 'SHIFT_NOT_FOUND' }
// return deletion result
return await this.prisma.$transaction(async (tx) => {
@ -80,6 +79,7 @@ export class ShiftsDeleteService {
return { success: true, data: shift.id };
});
} catch (error) {
console.error(error);
return { success: false, error: `SHIFT_NOT_FOUND` };
}
}

View File

@ -1,11 +1,9 @@
import { toDateFromString, toStringFromHHmm, toStringFromDate, toDateFromHHmm, overlaps, computeHours } from "src/common/utils/date-utils";
import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory";
import { shift_select } from "src/time-and-attendance/utils/selects.utils";
import { Normalized } from "src/time-and-attendance/utils/type.utils";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
@ -25,14 +23,18 @@ export class ShiftsUpdateService {
private readonly payPeriodEventService: PayPeriodEventService,
) { }
async updateOneOrManyShifts(shifts: ShiftDto[], email: string, is_from_timesheet: boolean = true): Promise<Result<boolean, string>> {
async updateOneOrManyShifts(
shifts: ShiftDto[],
email: string,
is_from_timesheet: boolean = true
): Promise<Result<boolean, string>> {
try {
//verify if array is empty or not
if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' };
//check for overlap inside dto objects
const overlap_check = await this.overlapChecker(shifts);
if (!overlap_check.success) return overlap_check;
const overlap_check = this.overlapChecker(shifts);
if (!overlap_check.success) return { success: false, error: `${overlap_check.error}` };
//calls the update functions and await the return of successfull result or not
const results = await Promise.allSettled(shifts.map(shift => this.updateShift(shift, email)));
@ -68,12 +70,10 @@ export class ShiftsUpdateService {
// returns array of updated shifts
return { success: true, data: true }
} catch (error) {
return { success: false, error }
return { success: false, error: `${error}` }
}
}
//_________________________________________________________________
// UPDATE
//_________________________________________________________________
async updateShift(dto: ShiftDto, email: string): Promise<Result<ShiftDto, string>> {
try {
const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, toDateFromString(dto.date));
@ -164,6 +164,7 @@ export class ShiftsUpdateService {
return { success: true, data: shift };
} catch (error) {
console.error(error);
return { success: false, error: `INVALID_SHIFT` };
}
}
@ -185,7 +186,7 @@ export class ShiftsUpdateService {
};
}
private overlapChecker = async (shifts: ShiftDto[]): Promise<Result<void, string>> => {
private overlapChecker = (shifts: ShiftDto[]) => {
for (let i = 0; i < shifts.length; i++) {
for (let j = i + 1; j < shifts.length; j++) {
const shift_a = shifts[i];

View File

@ -1,11 +1,9 @@
import { Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { ShiftsUpdateService } from "src/time-and-attendance/shifts/services/shifts-update.service";
import { ShiftsDeleteService } from "src/time-and-attendance/shifts/services/shifts-delete.service";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Result } from "src/common/errors/result-error.factory";
import { Access } from "src/common/decorators/module-access.decorators";
@ -20,37 +18,55 @@ export class ShiftController {
@Post('create')
@ModuleAccessAllowed(ModulesEnum.timesheets)
createBatch(@Access('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> {
createBatch(
@Access('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.create_service.createOneOrManyShifts(email, dtos);
}
@Post('create/:email')
@ModuleAccessAllowed(ModulesEnum.timesheets_approval)
createBatchByTimesheetsApproval(@Param('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> {
createBatchByTimesheetsApproval(
@Param('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.create_service.createOneOrManyShifts(email, dtos, false);
}
@Patch('update')
@ModuleAccessAllowed(ModulesEnum.timesheets)
updateBatch(@Access('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> {
updateBatch(
@Access('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.update_service.updateOneOrManyShifts(dtos, email);
}
@Patch('update/:email')
@ModuleAccessAllowed(ModulesEnum.timesheets_approval)
updateBatchByTimesheetApproval(@Param('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> {
updateBatchByTimesheetApproval(
@Param('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.update_service.updateOneOrManyShifts(dtos, email, false);
}
@Delete(':shift_id')
@ModuleAccessAllowed(ModulesEnum.timesheets)
remove(@Access('email') email: string, @Param('shift_id') shift_id: number): Promise<Result<number, string>> {
remove(
@Access('email') email: string,
@Param('shift_id') shift_id: number
): Promise<Result<number, string>> {
return this.delete_service.deleteShift(shift_id, email);
}
@Delete(':shift_id/:email')
@ModuleAccessAllowed(ModulesEnum.timesheets)
removeByTimesheetApproval(@Param('shift_id') shift_id: number, @Param('email') email: string): Promise<Result<number, string>> {
removeByTimesheetApproval(
@Param('shift_id') shift_id: number,
@Param('email') email: string
): Promise<Result<number, string>> {
return this.delete_service.deleteShift(shift_id, email, false);
}
}

View File

@ -2,12 +2,14 @@ import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validat
export class ShiftDto {
@IsInt() @IsOptional() id?: number;
@IsInt() timesheet_id!: number;
@IsString() type!: string;
@IsString() date!: string;
@IsString() start_time!: string;
@IsString() end_time!: string;
@IsBoolean() is_approved!: boolean;
@IsBoolean() is_remote!: boolean;
@IsInt() timesheet_id: number;
@IsString() type: string;
@IsString() date: string;
@IsString() start_time: string;
@IsString() end_time: string;
@IsBoolean() is_approved: boolean;
@IsBoolean() is_remote: boolean;
@IsOptional() @IsString() @MaxLength(280) comment?: string;
}
}
export type BillableShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'VACATION' | 'HOLIDAY' | 'SICK' | 'OVERTIME';

View File

@ -1 +0,0 @@
export type BillableShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'VACATION' | 'HOLIDAY' | 'SICK' | 'OVERTIME';

View File

@ -1,6 +1,5 @@
import { Module } from '@nestjs/common';
import { ShiftController } from 'src/time-and-attendance/shifts/shift.controller';
import { ShiftsCreateService } from 'src/time-and-attendance/shifts/services/shifts-create.service';
import { ShiftsDeleteService } from 'src/time-and-attendance/shifts/services/shifts-delete.service';
@ -12,8 +11,12 @@ import { PaidTimeOffBankHoursService } from 'src/time-and-attendance/paid-time-o
import { PayPeriodEventService } from 'src/time-and-attendance/pay-period/services/pay-period-event.service';
@Module({
imports: [PaidTimeOffModule],
controllers: [ShiftController],
imports: [
PaidTimeOffModule
],
controllers: [
ShiftController
],
providers: [
ShiftsCreateService,
ShiftsUpdateService,
@ -21,7 +24,7 @@ import { PayPeriodEventService } from 'src/time-and-attendance/pay-period/servic
VacationService,
BankedHoursService,
PaidTimeOffBankHoursService,
PayPeriodEventService,
PayPeriodEventService,
],
exports: [
ShiftsCreateService,

View File

@ -5,38 +5,31 @@ import { BankedHoursService } from "src/time-and-attendance/domains/services/ban
import { PaidTimeOffModule } from "src/time-and-attendance/paid-time-off/paid-time-off.module";
import { PaidTimeOffController } from "src/time-and-attendance/paid-time-off/paid-time-off.controller";
import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service";
import { ExpenseController } from "src/time-and-attendance/expenses/expense.controller";
import { ExpenseCreateService } from "src/time-and-attendance/expenses/services/expense-create.service";
import { ExpenseUpdateService } from "src/time-and-attendance/expenses/services/expense-update.service";
import { ExpenseDeleteService } from "src/time-and-attendance/expenses/services/expense-delete.service";
import { ExpensesModule } from "src/time-and-attendance/expenses/expenses.module";
import { TimesheetController } from "src/time-and-attendance/timesheets/timesheet.controller";
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-employee-overview.service";
import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module";
import { PayPeriodsController } from "src/time-and-attendance/pay-period/pay-periods.controller";
import { GetOverviewService } from "src/time-and-attendance/pay-period/services/pay-periods-build-overview.service";
import { PayPeriodsQueryService } from "src/time-and-attendance/pay-period/services/pay-periods-query.service";
import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service";
import { CsvExportModule } from "src/time-and-attendance/exports/csv-exports.module";
import { CsvExportService } from "src/time-and-attendance/exports/services/csv-exports.service";
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
import { CsvExportController } from "src/time-and-attendance/exports/csv-exports.controller";
import { ShiftController } from "src/time-and-attendance/shifts/shift.controller";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { ShiftsUpdateService } from "src/time-and-attendance/shifts/services/shifts-update.service";
import { ShiftsDeleteService } from "src/time-and-attendance/shifts/services/shifts-delete.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsController } from "src/time-and-attendance/schedule-presets/schedule-presets.controller";
import { SchedulePresetsModule } from "src/time-and-attendance/schedule-presets/schedule-presets.module";
@ -94,5 +87,7 @@ import { PayPeriodEventService } from "./pay-period/services/pay-period-event.se
PaidTimeOffBankHoursService,
PayPeriodEventService,
],
exports: [TimesheetApprovalService],
exports: [
TimesheetApprovalService
],
}) export class TimeAndAttendanceModule { };

View File

@ -1,56 +1,62 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, Timesheets } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Timesheets } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PrismaPostgresService, TransactionClient } from "prisma/postgres/prisma-postgres.service";
import { BaseApprovalService } from "src/common/shared/base-approval.service";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
@Injectable()
export class TimesheetApprovalService extends BaseApprovalService<Timesheets>{
@Injectable()
export class TimesheetApprovalService extends BaseApprovalService<Timesheets> {
constructor(
prisma: PrismaPostgresService,
){super(prisma)}
) { super(prisma) }
//_____________________________________________________________________________________________
// APPROVAL AND DELEGATE METHODS
//_____________________________________________________________________________________________
protected get delegate() {
return this.prisma.timesheets;
}
protected delegateFor(tx: TransactionClient) {
protected delegateFor(
tx: TransactionClient
) {
return tx.timesheets;
}
async updateApproval(id: number, is_approved: boolean): Promise<Timesheets> {
return this.prisma.$transaction((tx) =>
async updateApproval(
id: number,
is_approved: boolean
): Promise<Timesheets> {
return this.prisma.$transaction((tx) =>
this.updateApprovalWithTransaction(tx, id, is_approved),
);
}
async cascadeApprovalWithtx(tx: TransactionClient, timesheet_id: number, is_approved: boolean): Promise<Timesheets> {
async cascadeApprovalWithtx(
tx: TransactionClient,
timesheet_id: number,
is_approved: boolean
): Promise<Timesheets> {
const timesheet = await this.updateApprovalWithTransaction(tx, timesheet_id, is_approved);
await tx.shifts.updateMany({
where: { timesheet_id: timesheet_id },
data: { is_approved: is_approved },
data: { is_approved: is_approved },
});
await tx.expenses.updateMany({
where: { timesheet_id: timesheet_id },
data: { is_approved: is_approved },
data: { is_approved: is_approved },
});
return timesheet;
}
async approveTimesheetById( timesheet_id: number, is_approved: boolean){
async approveTimesheetById(
timesheet_id: number,
is_approved: boolean
) {
return this.prisma.$transaction(async (tx) => {
const timesheet = await tx.timesheets.findUnique({
where: { id: timesheet_id },
select: { id: true },
});
if(!timesheet) throw new NotFoundException(`Timesheet with id: ${timesheet_id} not found`);
if (!timesheet) throw new NotFoundException(`Timesheet with id: ${timesheet_id} not found`);
await this.cascadeApprovalWithtx(tx, timesheet_id, is_approved);
return tx.timesheets.findUnique({
@ -59,4 +65,4 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
});
});
}
}
}

View File

@ -15,10 +15,12 @@ export class GetTimesheetsOverviewService {
private readonly emailResolver: EmailToIdResolver,
) { }
//-----------------------------------------------------------------------------------
// GET TIMESHEETS FOR A SELECTED EMPLOYEE
//-----------------------------------------------------------------------------------
async getTimesheetsForEmployeeByPeriod(email: string, pay_year: number, pay_period_no: number, employee_email?: string): Promise<Result<Timesheets, string>> {
async getTimesheetsForEmployeeByPeriod(
email: string,
pay_year: number,
pay_period_no: number,
employee_email?: string
): Promise<Result<Timesheets, string>> {
try {
const account_email = employee_email ?? email;
@ -82,11 +84,11 @@ export class GetTimesheetsOverviewService {
}
}
//-----------------------------------------------------------------------------------
// MAPPERS & HELPERS
//-----------------------------------------------------------------------------------
//fetch timesheet's infos
private async loadTimesheets(employee_id: number, period_start: Date, period_end: Date) {
private async loadTimesheets(
employee_id: number,
period_start: Date,
period_end: Date
) {
return this.prisma.timesheets.findMany({
where: { employee_id, start_date: { gte: period_start, lte: period_end } },
include: {
@ -98,7 +100,10 @@ export class GetTimesheetsOverviewService {
});
}
private ensureTimesheet = async (employee_id: number, start_date: Date | string) => {
private ensureTimesheet = async (
employee_id: number,
start_date: Date | string
) => {
const start = toDateFromString(start_date);
let row = await this.prisma.timesheets.findFirst({

View File

@ -1,15 +1,18 @@
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
import { toDateFromString, sevenDaysFrom, toStringFromDate, toHHmmFromDate } from "src/common/utils/date-utils";
import { BankCodeDto } from "src/time-and-attendance/bank-codes/bank-codes.dto";
import { Timesheet } from "src/time-and-attendance/timesheets/timesheet.dto";
export const mapOneTimesheet = async (timesheet: Prisma.TimesheetsGetPayload<{
include: {
employee: { include: { user } },
shift: { include: { bank_code }, orderBy: { start_time: 'asc' } },
expense: { include: { bank_code } },
}
}>): Promise<Timesheet> => {
export const mapOneTimesheet = (
timesheet: Prisma.TimesheetsGetPayload<{
include: {
employee: { include: { user } },
shift: { include: { bank_code }, orderBy: { start_time: 'asc' } },
expense: { include: { bank_code } },
}
}>
): Timesheet => {
//converts string to UTC date format
const start = toDateFromString(timesheet.start_date);
const day_dates = sevenDaysFrom(start);
@ -23,7 +26,7 @@ export const mapOneTimesheet = async (timesheet: Prisma.TimesheetsGetPayload<{
shifts_by_date.set(date_string, arr);
}
//map of expenses by days
const expenses_by_date = new Map<string, Prisma.ExpensesGetPayload<{ include: { bank_code: {} } }>[]>();
const expenses_by_date = new Map<string, Prisma.ExpensesGetPayload<{ include: { bank_code: Prisma.BankCodesDefaultArgs } }>[]>();
for (const expense of timesheet.expense) {
const date_string = toStringFromDate(expense.date);
const arr = expenses_by_date.get(date_string) ?? [];
@ -75,11 +78,11 @@ export const mapOneTimesheet = async (timesheet: Prisma.TimesheetsGetPayload<{
const hours = diffOfHours(shift.start_time, shift.end_time);
const subgroup = hoursSubGroupFromBankCode(shift.bank_code);
const worked_weekly_hours = weekly_hours.regular + weekly_hours.emergency + weekly_hours.banking + weekly_hours.evening + weekly_hours.overtime + weekly_hours.holiday;
if ((worked_weekly_hours + hours <= 40) && (subgroup === 'regular' || subgroup === 'evening')) {
daily_hours['overtime'] += Math.max(daily_hours[subgroup] + hours - 8, 0);
weekly_hours['overtime'] += Math.max(daily_hours[subgroup] + hours - 8, 0);
weekly_hours[subgroup] += Math.min(hours, 8 - daily_hours[subgroup]);
daily_hours[subgroup] += Math.min(hours, 8 - daily_hours[subgroup]);
} else if (subgroup === 'regular' || subgroup === 'evening') {
@ -150,7 +153,7 @@ const diffOfHours = (a: Date, b: Date): number => {
const num = (value: any): number => { return value ? Number(value) : 0 };
// shift's subgroup types
const hoursSubGroupFromBankCode = (bank_code: any): keyof TotalHours => {
const hoursSubGroupFromBankCode = (bank_code: BankCodeDto): keyof TotalHours => {
const type = bank_code.type;
if (type.includes('EVENING')) return 'evening';
if (type.includes('EMERGENCY')) return 'emergency';
@ -164,7 +167,7 @@ const hoursSubGroupFromBankCode = (bank_code: any): keyof TotalHours => {
}
// expense's subgroup types
const expenseSubgroupFromBankCode = (bank_code: any): keyof TotalExpenses => {
const expenseSubgroupFromBankCode = (bank_code: BankCodeDto): keyof TotalExpenses => {
const type = bank_code.type;
if (type.includes('MILEAGE')) return 'mileage';
if (type.includes('PER_DIEM')) return 'per_diem';

View File

@ -6,12 +6,16 @@ import { GetTimesheetsOverviewService } from 'src/time-and-attendance/timesheets
import { EmailToIdResolver } from 'src/common/mappers/email-id.mapper';
@Module({
controllers: [TimesheetController],
providers: [
controllers: [
TimesheetController
],
providers: [
GetTimesheetsOverviewService,
EmailToIdResolver,
TimesheetApprovalService,
],
exports: [TimesheetApprovalService],
exports: [
TimesheetApprovalService
],
})
export class TimesheetsModule {}
export class TimesheetsModule { }