clean(modules): cleaned unused dto files and added validation. Changed error messages to match i18n

This commit is contained in:
Matthieu Haineault 2025-11-18 14:55:42 -05:00
parent 3ceb2e0955
commit 48f1220a4e
22 changed files with 130 additions and 320 deletions

View File

@ -207,114 +207,6 @@
] ]
} }
}, },
"/schedule-presets/create": {
"post": {
"operationId": "SchedulePresetsController_createPreset",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SchedulePresetsDto"
}
}
}
},
"responses": {
"201": {
"description": ""
}
},
"tags": [
"SchedulePresets"
]
}
},
"/schedule-presets/update/{preset_id}": {
"patch": {
"operationId": "SchedulePresetsController_updatePreset",
"parameters": [
{
"name": "preset_id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SchedulePresetsUpdateDto"
}
}
}
},
"responses": {
"200": {
"description": ""
}
},
"tags": [
"SchedulePresets"
]
}
},
"/schedule-presets/delete/{preset_id}": {
"delete": {
"operationId": "SchedulePresetsController_deletePreset",
"parameters": [
{
"name": "preset_id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"SchedulePresets"
]
}
},
"/schedule-presets/find-list": {
"get": {
"operationId": "SchedulePresetsController_findListById",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"SchedulePresets"
]
}
},
"/schedule-presets/apply-presets": {
"post": {
"operationId": "SchedulePresetsController_applyPresets",
"parameters": [],
"responses": {
"201": {
"description": ""
}
},
"tags": [
"SchedulePresets"
]
}
},
"/expense/create": { "/expense/create": {
"post": { "post": {
"operationId": "ExpenseController_create", "operationId": "ExpenseController_create",
@ -719,14 +611,6 @@
} }
}, },
"schemas": { "schemas": {
"SchedulePresetsDto": {
"type": "object",
"properties": {}
},
"SchedulePresetsUpdateDto": {
"type": "object",
"properties": {}
},
"ExpenseDto": { "ExpenseDto": {
"type": "object", "type": "object",
"properties": {} "properties": {}

View File

@ -17,7 +17,7 @@ export class BankCodesResolver {
where: { type }, where: { type },
select: { id: true, modifier: true }, select: { id: true, modifier: true },
}); });
if (!bank) return { success: false, error: `Unknown bank code type: ${type}` }; if (!bank) return { success: false, error: `INVALID_TYPE` };
return { success: true, data: { id: bank.id, modifier: bank.modifier } }; return { success: true, data: { id: bank.id, modifier: bank.modifier } };
}; };
@ -30,7 +30,7 @@ export class BankCodesResolver {
where: { type }, where: { type },
select: { id: true }, select: { id: true },
}); });
if (!bank_code) return { success: false, error: `Unkown bank type: ${type}` }; if (!bank_code) return { success: false, error: `INVALID_TYPE` };
return { success: true, data: bank_code.id }; return { success: true, data: bank_code.id };
} }
@ -42,7 +42,7 @@ export class BankCodesResolver {
where: { id: bank_code_id }, where: { id: bank_code_id },
select: { type: true }, select: { type: true },
}); });
if (!bank_code) return { success: false, error: `Type with id : ${bank_code_id} not found` } if (!bank_code) return { success: false, error: `INVALID_TYPE` }
return { success: true, data: bank_code.type }; return { success: true, data: bank_code.type };
} }

View File

@ -18,7 +18,7 @@ export class EmailToIdResolver {
where: { user: { email } }, where: { user: { email } },
select: { id: true }, select: { id: true },
}); });
if (!employee) return { success: false, error: `Employee with email:${email} not found` }; if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
return { data: employee.id, success: true }; return { data: employee.id, success: true };
} }
@ -30,7 +30,7 @@ export class EmailToIdResolver {
where: { email }, where: { email },
select: { id: true }, select: { id: true },
}); });
if (!user) return { success: false, error: `User with email:${email} not found` }; if (!user) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
return { success: true, data: user.id }; return { success: true, data: user.id };
} }
} }

View File

@ -15,7 +15,7 @@ export class FullNameResolver {
where: { id: employee_id }, where: { id: employee_id },
select: { user: { select: {first_name: true, last_name: true} } }, select: { user: { select: {first_name: true, last_name: true} } },
}); });
if(!employee) return { success: false, error: `Unknown user with id ${employee_id}`} if(!employee) return { success: false, error: `INVALID_EMPLOYEE`}
const full_name = ( employee.user.first_name + " " + employee.user.last_name ) || " "; const full_name = ( employee.user.first_name + " " + employee.user.last_name ) || " ";
return {success: true, data: full_name }; return {success: true, data: full_name };

View File

@ -32,7 +32,7 @@ export class ShiftIdResolver {
}, },
select: { id: true }, select: { id: true },
}); });
if (!shift) return { success: false, error: `shift not found` } if (!shift) return { success: false, error: `SHIFT_NOT_FOUND` }
return { success: true, data: shift.id }; return { success: true, data: shift.id };
}; };

View File

@ -25,7 +25,7 @@ export class EmployeeTimesheetResolver {
where: { employee_id : employee_id.data, start_date: start_date }, where: { employee_id : employee_id.data, start_date: start_date },
select: { id: true }, select: { id: true },
}); });
if(!timesheet) throw new NotFoundException(`timesheet not found`); if(!timesheet) throw new NotFoundException(`TIMESHEET_NOT_FOUND`);
return { success: true, data: {id: timesheet.id} }; return { success: true, data: {id: timesheet.id} };
} }
} }

View File

@ -1,15 +0,0 @@
// import { BankCodeEntity } from "src/modules/bank-codes/dtos/bank-code-entity";
export class ExpenseEntity {
id: number;
timesheet_id: number;
bank_code_id: number;
attachment?:number | null;
date: Date;
amount?: number | null;
mileage?:number | null;
comment: string;
supervisor_comment?:string | null;
is_approved: boolean;
// bank_code?: BankCodeEntity;
}

View File

@ -1,12 +0,0 @@
export class GetExpenseDto {
id: number;
timesheet_id: number;
bank_code_id: number;
attachment?: number;
date: string;
comment: string;
mileage?: number;
amount?: number;
supervisor_comment?: string;
is_approved: boolean;
}

View File

@ -38,7 +38,7 @@ export class ExpenseUpsertService {
where: { start_date, employee_id: employee_id.data }, where: { start_date, employee_id: employee_id.data },
select: { id: true, employee_id: true }, select: { id: true, employee_id: true },
}); });
if (!timesheet) return { success: false, error: `Timesheet with id : ${dto.timesheet_id} not found` }; if (!timesheet) return { success: false, error: `TIMESHEET_NOT_FOUND` };
//create a new expense //create a new expense
const expense = await this.prisma.expenses.create({ const expense = await this.prisma.expenses.create({
@ -50,7 +50,7 @@ export class ExpenseUpsertService {
//return the newly created expense with id //return the newly created expense with id
select: expense_select, select: expense_select,
}); });
if (!expense) return { success: false, error: `An error occured during creation. Expense is invalid` }; if (!expense) return { success: false, error: `INVALID_EXPENSE` };
//build an object to return to the frontend to display //build an object to return to the frontend to display
const created: ExpenseDto = { const created: ExpenseDto = {
@ -65,7 +65,7 @@ export class ExpenseUpsertService {
return { success: true, data: created }; return { success: true, data: created };
} catch (error) { } catch (error) {
return { success: false, error: `An error occured during creation. Expense not created : ` + error }; return { success: false, error: 'INVALID_EXPENSE' };
} }
} }
@ -88,14 +88,14 @@ export class ExpenseUpsertService {
where: { start_date: new_timesheet_start_date, employee_id: employee_id.data }, where: { start_date: new_timesheet_start_date, employee_id: employee_id.data },
select: timesheet_select, select: timesheet_select,
}); });
if (!timesheet) return { success: false, error: `Timesheet ${dto.timesheet_id} not found` } if (!timesheet) return { success: false, error: `TIMESHEET_NOT_FOUND` }
//checks for modifications //checks for modifications
const data: Prisma.ExpensesUpdateInput = { const data: Prisma.ExpensesUpdateInput = {
...normed_expense.data, ...normed_expense.data,
is_approved: dto.is_approved, is_approved: dto.is_approved,
}; };
if (!data) return { success: false, error: `An error occured during normalization. Expense with id: ${dto.id} is invalid` } if (!data) return { success: false, error: `INVALID_EXPENSE` }
//push updates and get updated datas //push updates and get updated datas
const expense = await this.prisma.expenses.update({ const expense = await this.prisma.expenses.update({
@ -103,7 +103,7 @@ export class ExpenseUpsertService {
data, data,
select: expense_select, select: expense_select,
}); });
if (!expense) return { success: false, error: `An error occured during update. Expense with id: ${dto.id} was not updated` } if (!expense) return { success: false, error: 'INVALID_EXPENSE' }
//build an object to return to the frontend //build an object to return to the frontend
const updated: ExpenseDto = { const updated: ExpenseDto = {
@ -117,7 +117,7 @@ export class ExpenseUpsertService {
}; };
return { success: true, data: updated }; return { success: true, data: updated };
} catch (error) { } catch (error) {
return { success: false, error: (`Expense with id: ${dto.id} generated an error:` + error) }; return { success: false, error: 'EXPENSE_NOT_FOUND' };
} }
} }
//_________________________________________________________________ //_________________________________________________________________
@ -130,20 +130,14 @@ export class ExpenseUpsertService {
where: { id: expense_id }, where: { id: expense_id },
select: { id: true }, select: { id: true },
}); });
if (!expense) return { if (!expense) return { success: false, error: `EXPENSE_NOT_FOUND` };
success: false,
error: `An error occured during removal. Expense with id :${expense_id} was not found `
};
await tx.expenses.delete({ where: { id: expense.id } }); await tx.expenses.delete({ where: { id: expense.id } });
return { success: true, data: expense.id }; return { success: true, data: expense.id };
}); });
return { success: true, data: expense_id }; return { success: true, data: expense_id };
} catch (error) { } catch (error) {
return { return { success: false, error: `EXPENSE_NOT_FOUND` };
success: false,
error: `An error occured during removal. Expense with id :${expense_id} generated an error: ` + error
};
} }
} }
@ -162,7 +156,7 @@ export class ExpenseUpsertService {
const date = toDateFromString(dto.date); const date = toDateFromString(dto.date);
const type = await this.typeResolver.findBankCodeIDByType(dto.type); const type = await this.typeResolver.findBankCodeIDByType(dto.type);
if (!type.success) return { success: false, error: 'Bank-type not found' } if (!type.success) return { success: false, error: 'INVALID_EXPENSE_TYPE' }
return { return {
success: true, success: true,

View File

@ -4,6 +4,8 @@ import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
import { PayPeriodsQueryService } from "./pay-periods-query.service"; import { PayPeriodsQueryService } from "./pay-periods-query.service";
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service"; import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
//change promise to return result pattern
@Injectable() @Injectable()
export class PayPeriodsCommandService { export class PayPeriodsCommandService {
constructor( constructor(

View File

@ -9,12 +9,14 @@ import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
export class PayPeriodsQueryService { export class PayPeriodsQueryService {
constructor(private readonly prisma: PrismaService) { } constructor(private readonly prisma: PrismaService) { }
//change promise to return result pattern
async getOverview(pay_period_no: number): Promise<PayPeriodOverviewDto> { async getOverview(pay_period_no: number): Promise<PayPeriodOverviewDto> {
const period = await this.prisma.payPeriods.findFirst({ const period = await this.prisma.payPeriods.findFirst({
where: { pay_period_no }, where: { pay_period_no },
orderBy: { pay_year: "desc" }, orderBy: { pay_year: "desc" },
}); });
if (!period) throw new NotFoundException(`Period #${pay_period_no} not found`); if (!period) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
return this.buildOverview({ return this.buildOverview({
period_start: period.period_start, period_start: period.period_start,
@ -78,7 +80,7 @@ export class PayPeriodsQueryService {
Promise<PayPeriodOverviewDto> { Promise<PayPeriodOverviewDto> {
// 1) Search for the period // 1) Search for the period
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no: period_no } }); const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no: period_no } });
if (!period) throw new NotFoundException(`Pay period ${pay_year}-${period_no} not found`); if (!period) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
// 2) fetch supervisor // 2) fetch supervisor
const supervisor = await this.prisma.employees.findFirst({ const supervisor = await this.prisma.employees.findFirst({
@ -345,7 +347,7 @@ export class PayPeriodsQueryService {
where: { pay_period_no: period_no }, where: { pay_period_no: period_no },
orderBy: { pay_year: "desc" }, orderBy: { pay_year: "desc" },
}); });
if (!row) throw new NotFoundException(`Pay period #${period_no} not found`); if (!row) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
return mapPayPeriodToDto(row); return mapPayPeriodToDto(row);
} }
@ -384,7 +386,7 @@ export class PayPeriodsQueryService {
const pay_year = payYearOfDate(date); const pay_year = payYearOfDate(date);
const periods = listPayYear(pay_year); const periods = listPayYear(pay_year);
const hit = periods.find(period => date >= period.period_start && date <= period.period_end); const hit = periods.find(period => date >= period.period_start && date <= period.period_end);
if (!hit) throw new NotFoundException(`No period found for ${date}`); if (!hit) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
return { return {
pay_period_no: hit.period_no, pay_period_no: hit.period_no,

View File

@ -1,5 +1,5 @@
import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "class-validator"; import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "class-validator";
import { SchedulePresetShiftsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-preset-shifts.dto"; import { SchedulePresetShiftsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-preset-shifts.dto";
export class SchedulePresetsDto { export class SchedulePresetsDto {

View File

@ -1,4 +0,0 @@
import { SchedulePresetsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto";
export class SchedulePresetsUpdateDto extends SchedulePresetsDto{}

View File

@ -1,21 +1,21 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller"; // import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller";
import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service"; // import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service"; // import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service"; // import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service";
@Module({ @Module({
controllers: [SchedulePresetsController], controllers: [/*SchedulePresetsController*/],
providers: [ // providers: [
SchedulePresetsUpsertService, // SchedulePresetsUpsertService,
SchedulePresetsGetService, // SchedulePresetsGetService,
SchedulePresetsApplyService, // SchedulePresetsApplyService,
], // ],
exports:[ exports:[
SchedulePresetsUpsertService, // SchedulePresetsUpsertService,
SchedulePresetsGetService, // SchedulePresetsGetService,
SchedulePresetsApplyService, // SchedulePresetsApplyService,
], ],
}) export class SchedulePresetsModule {} }) export class SchedulePresetsModule {}

View File

@ -1,10 +1,10 @@
import { Body, Controller, Delete, Param, Patch, Post, Req } from "@nestjs/common"; import { Body, Controller, Delete, Param, Patch, Post, Req } from "@nestjs/common";
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes"; import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { ShiftsCreateService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-create.service"; import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service"; import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/services/shifts-update-delete.service";
@Controller('shift') @Controller('shift')

View File

@ -1,12 +0,0 @@
export class GetShiftDto {
shift_id: number;
timesheet_id: number;
type: string;
date: string;
start_time: string;
end_time: string;
is_remote: boolean;
is_approved: boolean;
comment?: string;
}

View File

@ -4,9 +4,9 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toHHmmFromString } from "src/common/utils/date-utils"; import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toHHmmFromString } from "src/common/utils/date-utils";
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
@Injectable() @Injectable()
export class ShiftsCreateService { export class ShiftsCreateService {
@ -63,26 +63,14 @@ export class ShiftsCreateService {
try { try {
//transform string format to date and HHmm //transform string format to date and HHmm
const normed_shift = await this.normalizeShiftDto(dto); const normed_shift = await this.normalizeShiftDto(dto);
if(!normed_shift.success) return { success: false, error: 'An error occured during normalization' } if (!normed_shift.success) return { success: false, error: normed_shift.error };
if (normed_shift.data.end_time <= normed_shift.data.start_time) return { if (normed_shift.data.end_time <= normed_shift.data.start_time) return { success: false, error: `INVALID_SHIFT_TIME` };
success: false,
error: `INVALID_SHIFT - `
+ `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},`
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
+ `date: ${toStringFromDate(normed_shift.data.date)}.`
}
//fetch the right timesheet //fetch the right timesheet
const timesheet = await this.prisma.timesheets.findUnique({ const timesheet = await this.prisma.timesheets.findUnique({
where: { id: dto.timesheet_id, employee_id }, where: { id: dto.timesheet_id, employee_id },
select: timesheet_select, select: timesheet_select,
}); });
if (!timesheet) return { if (!timesheet) return { success: false, error: `INVALID_TIMESHEET` };
success: false,
error: `INVALID_TIMESHEET -`
+ `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},`
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
+ `date: ${toStringFromDate(normed_shift.data.date)}.`
}
//finds bank_code_id using the type //finds bank_code_id using the type
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type); const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
if (!bank_code_id.success) return { success: false, error: bank_code_id.error }; if (!bank_code_id.success) return { success: false, error: bank_code_id.error };
@ -102,13 +90,7 @@ export class ShiftsCreateService {
{ start: existing_start, end: existing_end, date: existing_date }, { start: existing_start, end: existing_end, date: existing_date },
); );
if (has_overlap) { if (has_overlap) {
return { return { success: false, error: `SHIFT_OVERLAP` };
success: false,
error: `SHIFT_OVERLAP`
+ `new shift: ${toStringFromHHmm(normed_shift.data.start_time)}${toStringFromHHmm(normed_shift.data.end_time)} `
+ `existing shift: ${toStringFromHHmm(existing.start_time)}${toStringFromHHmm(existing.end_time)} `
+ `date: ${toStringFromDate(normed_shift.data.date)})`,
}
} }
} }
@ -139,7 +121,7 @@ export class ShiftsCreateService {
} }
return { success: true, data: shift }; return { success: true, data: shift };
} catch (error) { } catch (error) {
return { success: false, error: `An error occured during creation, invalid data` }; return { success: false, error: `INVALID_SHIFT` };
} }
} }
@ -148,13 +130,14 @@ export class ShiftsCreateService {
//_________________________________________________________________ //_________________________________________________________________
//converts all string hours and date to Date and HHmm formats //converts all string hours and date to Date and HHmm formats
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => { private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type); const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
if(!bank_code_id.success) return { success: false, error: 'Bank_code not found'} if (!bank_code_id.success) return { success: false, error: 'INVALID_SHIFT' }
//TODO: validate date and time to ensure "banana" is not accepted using an if statement and a REGEX
const date = toDateFromString(dto.date); const date = toDateFromString(dto.date);
const start_time = toHHmmFromString(dto.start_time); const start_time = toHHmmFromString(dto.start_time);
const end_time = toHHmmFromString(dto.end_time); const end_time = toHHmmFromString(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

@ -3,10 +3,10 @@ import { PrismaService } from "src/prisma/prisma.service";
import { shift_select } from "src/time-and-attendance/utils/selects.utils"; import { shift_select } from "src/time-and-attendance/utils/selects.utils";
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Normalized } from "src/time-and-attendance/utils/type.utils"; import { Normalized } from "src/time-and-attendance/utils/type.utils";
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper"; import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
import { toDateFromString, toStringFromHHmm, toStringFromDate, toHHmmFromString, overlaps } from "src/common/utils/date-utils"; import { toDateFromString, toStringFromHHmm, toStringFromDate, toHHmmFromString, overlaps } from "src/common/utils/date-utils";
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
@Injectable() @Injectable()
export class ShiftsUpdateDeleteService { export class ShiftsUpdateDeleteService {
@ -67,18 +67,12 @@ export class ShiftsUpdateDeleteService {
where: { id: dto.id, timesheet_id: timesheet.data.id }, where: { id: dto.id, timesheet_id: timesheet.data.id },
select: shift_select, select: shift_select,
}); });
if (!original) return { success: false, error: `Shift with id: ${dto.id} not found` }; if (!original) return { success: false, error: `SHIFT_NOT_FOUND` };
//transform string format to date and HHmm //transform string format to date and HHmm
const normed_shift = await this.normalizeShiftDto(dto); const normed_shift = await this.normalizeShiftDto(dto);
if (!normed_shift.success) return { success: false, error: 'An error occured during normalization' } if (!normed_shift.success) return { success: false, error: normed_shift.error }
if (normed_shift.data.end_time <= normed_shift.data.start_time) return { if (normed_shift.data.end_time <= normed_shift.data.start_time) return { success: false, error: `INVALID_SHIFT` };
success: false,
error: `INVALID_SHIFT - `
+ `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},`
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
+ `date: ${toStringFromDate(normed_shift.data.date)}.`
};
//finds bank_code_id using the type //finds bank_code_id using the type
const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type); const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type);
@ -98,7 +92,7 @@ export class ShiftsUpdateDeleteService {
}, },
select: shift_select, select: shift_select,
}); });
if (!updated) return { success: false, error: ' An error occured during update, Invalid Datas' }; if (!updated) return { success: false, error: 'INVALID_SHIFT' };
// builds an object to return for display in the frontend // builds an object to return for display in the frontend
const shift: ShiftDto = { const shift: ShiftDto = {
@ -115,7 +109,7 @@ export class ShiftsUpdateDeleteService {
return { success: true, data: shift }; return { success: true, data: shift };
} catch (error) { } catch (error) {
return { success: false, error: `An error occured during update. Invalid Data` }; return { success: false, error: `INVALID_SHIFT` };
} }
} }
@ -131,13 +125,13 @@ export class ShiftsUpdateDeleteService {
where: { id: shift_id }, where: { id: shift_id },
select: { id: true, date: true, timesheet_id: true }, select: { id: true, date: true, timesheet_id: true },
}); });
if (!shift) return { success: false, error: `shift with id ${shift_id} not found ` }; if (!shift) return { success: false, error: `SHIFT_NOT_FOUND` };
await tx.shifts.delete({ where: { id: shift_id } }); await tx.shifts.delete({ where: { id: shift_id } });
return { success: true, data: shift.id }; return { success: true, data: shift.id };
}); });
} catch (error) { } catch (error) {
return { success: false, error: `INVALID_SHIFT, shift with id ${shift_id} not found` } return { success: false, error: `SHIFT_NOT_FOUND` }
} }
} }
@ -146,7 +140,7 @@ export class ShiftsUpdateDeleteService {
//_________________________________________________________________ //_________________________________________________________________
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => { private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type); const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
if (!bank_code_id.success) return { success: false, error: 'Bank_code not found' } if (!bank_code_id.success) return { success: false, error: 'INVALID_SHIFT' }
return { return {
success: true, success: true,
data: { data: {
@ -163,21 +157,13 @@ export class ShiftsUpdateDeleteService {
for (let j = i + 1; j < shifts.length; j++) { for (let j = i + 1; j < shifts.length; j++) {
const shift_a = shifts[i]; const shift_a = shifts[i];
const shift_b = shifts[j]; const shift_b = shifts[j];
if (shift_a.date !== shift_b.date || shift_a.id === shift_b.id) continue; if (shift_a.date !== shift_b.date || shift_a.id === shift_b.id) continue;
const has_overlap = overlaps( const has_overlap = overlaps(
{ start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) }, { start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) },
{ start: toHHmmFromString(shift_b.start_time), end: toHHmmFromString(shift_b.end_time) }, { start: toHHmmFromString(shift_b.start_time), end: toHHmmFromString(shift_b.end_time) },
); );
if (has_overlap) { if (has_overlap) return { success: false, error: `SHIFT_OVERLAP` };
return {
success: false,
error: `SHIFT_OVERLAP`
+ `new shift: ${shift_a.start_time}${shift_a.end_time} `
+ `existing shift: ${shift_b.start_time}${shift_b.end_time} `
+ `date: ${shift_a.date})`,
}
}
} }
} }
return { success: true, data: undefined } return { success: true, data: undefined }

View File

@ -1,8 +1,8 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ShiftController } from 'src/time-and-attendance/time-tracker/shifts/controllers/shift.controller'; import { ShiftController } from 'src/time-and-attendance/shifts/controllers/shift.controller';
import { ShiftsCreateService } from 'src/time-and-attendance/time-tracker/shifts/services/shifts-create.service'; import { ShiftsCreateService } from 'src/time-and-attendance/shifts/services/shifts-create.service';
import { ShiftsUpdateDeleteService } from 'src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service'; import { ShiftsUpdateDeleteService } from 'src/time-and-attendance/shifts/services/shifts-update-delete.service';
@Module({ @Module({
controllers: [ShiftController], controllers: [ShiftController],

View File

@ -4,14 +4,7 @@ import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-l
import { ExpenseController } from "src/time-and-attendance/expenses/controllers/expense.controller"; import { ExpenseController } from "src/time-and-attendance/expenses/controllers/expense.controller";
import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service"; import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service";
import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module"; import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module";
import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller";
import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service";
import { ShiftController } from "src/time-and-attendance/time-tracker/shifts/controllers/shift.controller";
import { ShiftsCreateService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-create.service";
import { ShiftsGetService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-get.service";
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service";
import { TimesheetController } from "src/time-and-attendance/timesheets/controllers/timesheet.controller"; import { TimesheetController } from "src/time-and-attendance/timesheets/controllers/timesheet.controller";
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service"; import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-get-overview.service"; import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-get-overview.service";
@ -26,6 +19,12 @@ import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/ser
import { CsvExportModule } from "src/modules/exports/csv-exports.module"; import { CsvExportModule } from "src/modules/exports/csv-exports.module";
import { CsvExportService } from "src/modules/exports/services/csv-exports.service"; import { CsvExportService } from "src/modules/exports/services/csv-exports.service";
import { CsvExportController } from "src/modules/exports/controllers/csv-exports.controller"; import { CsvExportController } from "src/modules/exports/controllers/csv-exports.controller";
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { ShiftController } from "src/time-and-attendance/shifts/controllers/shift.controller";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { ShiftsGetService } from "src/time-and-attendance/shifts/services/shifts-get.service";
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/services/shifts-update-delete.service";
@Module({ @Module({
imports: [ imports: [
@ -39,7 +38,7 @@ import { CsvExportController } from "src/modules/exports/controllers/csv-exports
controllers: [ controllers: [
TimesheetController, TimesheetController,
ShiftController, ShiftController,
SchedulePresetsController, // SchedulePresetsController,
ExpenseController, ExpenseController,
PayPeriodsController, PayPeriodsController,
CsvExportController, CsvExportController,
@ -51,7 +50,7 @@ import { CsvExportController } from "src/modules/exports/controllers/csv-exports
ShiftsCreateService, ShiftsCreateService,
ShiftsUpdateDeleteService, ShiftsUpdateDeleteService,
ExpenseUpsertService, ExpenseUpsertService,
SchedulePresetsUpsertService, // SchedulePresetsUpsertService,
SchedulePresetsGetService, SchedulePresetsGetService,
SchedulePresetsApplyService, SchedulePresetsApplyService,
EmailToIdResolver, EmailToIdResolver,

View File

@ -1,67 +1,70 @@
import { Type } from "class-transformer";
import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator";
export class TimesheetEntity { export class TimesheetEntity {
id: number; @IsInt() id: number;
employee_id: number; @IsInt() employee_id: number;
start_date: Date; @IsDate() start_date: Date;
is_approved: boolean; @IsBoolean() is_approved: boolean;
} }
export class Timesheets { export class Timesheets {
employee_fullname: string; @IsString() employee_fullname: string;
timesheets: Timesheet[]; @Type(() => Timesheet) timesheets: Timesheet[];
} }
export class Timesheet { export class Timesheet {
timesheet_id: number; @IsInt() timesheet_id: number;
is_approved: boolean; @IsBoolean() is_approved: boolean;
days: TimesheetDay[]; @Type(() => TimesheetDay) days: TimesheetDay[];
weekly_hours: TotalHours[]; @Type(() => TotalHours) weekly_hours: TotalHours[];
weekly_expenses: TotalExpenses[]; @Type(() => TotalExpenses) weekly_expenses: TotalExpenses[];
} }
export class TimesheetDay { export class TimesheetDay {
date: string; @IsString() date: string;
shifts: Shift[]; @Type(() => Shift) shifts: Shift[];
expenses: Expense[]; @Type(() => Expense) expenses: Expense[];
daily_hours: TotalHours[]; @Type(() => TotalHours) daily_hours: TotalHours[];
daily_expenses: TotalExpenses[]; @Type(() => TotalExpenses) daily_expenses: TotalExpenses[];
} }
export class TotalHours { export class TotalHours {
regular: number; @Type(() => Number) regular: number;
evening: number; @Type(() => Number) evening: number;
emergency: number; @Type(() => Number) emergency: number;
overtime: number; @Type(() => Number) overtime: number;
vacation: number; @Type(() => Number) vacation: number;
holiday: number; @Type(() => Number) holiday: number;
sick: number; @Type(() => Number) sick: number;
} }
export class TotalExpenses { export class TotalExpenses {
expenses: number; @Type(() => Number) expenses: number;
per_diem: number; @Type(() => Number) per_diem: number;
on_call: number; @Type(() => Number) on_call: number;
mileage: number; @Type(() => Number) mileage: number;
} }
export class Shift { export class Shift {
timesheet_id: number; @IsInt() timesheet_id: number;
date: string; @IsString() date: string;
start_time: string; @IsString() start_time: string;
end_time: string; @IsString() end_time: string;
type: string; @IsString() type: string;
is_remote: boolean; @IsBoolean() is_remote: boolean;
is_approved: boolean; @IsBoolean() is_approved: boolean;
shift_id?: number | null; @IsInt() @IsOptional() shift_id?: number | null;
comment?: string | null; @IsString() @IsOptional() comment?: string | null;
} }
export class Expense { export class Expense {
date: string; @IsString() date: string;
is_approved: boolean; @IsBoolean() is_approved: boolean;
type: string; @IsString() type: string;
comment: string; @IsString() comment: string;
amount?: number; @Type(() => Number) @IsOptional() amount?: number;
mileage?: number; @Type(() => Number) @IsOptional() mileage?: number;
attachment?: string; @IsString() @IsOptional() attachment?: string;
id?: number | null; @IsOptional() @IsInt() id?: number | null;
supervisor_comment?: string | null; @IsString() @IsOptional() supervisor_comment?: string | null;
} }

View File

@ -39,11 +39,11 @@ export class GetTimesheetsOverviewService {
try { try {
//find period using year and period_no //find period using year and period_no
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no } }); const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no } });
if (!period) return { success: false, error: `Pay period ${pay_year}-${pay_period_no} not found` }; if (!period) return { success: false, error: `PAY_PERIOD_NOT_FOUND` };
//fetch the employee_id using the email //fetch the employee_id using the email
const employee_id = await this.emailResolver.findIdByEmail(email); const employee_id = await this.emailResolver.findIdByEmail(email);
if (!employee_id.success) return { success: false, error: `employee with email: ${email} not found` + employee_id.error } if (!employee_id.success) return { success: false, error: employee_id.error }
//loads the timesheets related to the fetched pay-period //loads the timesheets related to the fetched pay-period
let rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end); let rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end);
@ -71,7 +71,7 @@ export class GetTimesheetsOverviewService {
where: { id: employee_id.data }, where: { id: employee_id.data },
include: { user: true }, include: { user: true },
}); });
if (!employee) return { success: false, error: `Employee #${employee_id} not found` } if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` }
//builds employee full name //builds employee full name
const user = employee.user; const user = employee.user;
@ -80,11 +80,11 @@ export class GetTimesheetsOverviewService {
//maps all timesheet's infos //maps all timesheet's infos
const timesheets = await Promise.all(rows.map((timesheet) => this.mapOneTimesheet(timesheet))); const timesheets = await Promise.all(rows.map((timesheet) => this.mapOneTimesheet(timesheet)));
if (!timesheets) return { success: false, error: 'an error occured during the mapping of a timesheet' } if (!timesheets) return { success: false, error: 'INVALID_TIMESHEET' }
return { success: true, data: { employee_fullname, timesheets } }; return { success: true, data: { employee_fullname, timesheets } };
} catch (error) { } catch (error) {
return { success: false, error: 'timesheet failed to load: ' + pay_year + pay_period_no } return { success: false, error: 'TIMESHEET_NOT_FOUND' }
} }
} }