From 1d9eaeab30060e353ca6393cdfd011f0b560fc6e Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Wed, 12 Nov 2025 09:16:37 -0500 Subject: [PATCH] feat(Result): ajusted return values to match Result pattern. --- npm | 0 src/common/errors/result-error.factory.ts | 12 +-- .../domains/services/overtime.service.ts | 20 ++++- .../controllers/expense.controller.ts | 1 - .../services/expense-upsert.service.ts | 3 +- .../mappers/leave-requests.mapper.ts | 4 +- .../utils/leave-request.transform.ts | 4 +- .../services/schedule-presets-get.service.ts | 1 - .../schedule-presets-upsert.service.ts | 64 ++++++++-------- .../shifts/controllers/shift.controller.ts | 1 - .../shifts/services/shifts-create.service.ts | 43 ++++++----- .../services/shifts-update-delete.service.ts | 73 +++++++++++++------ .../timesheet-get-overview.service.ts | 30 ++++++-- .../utils/resolve-bank-type-id.utils.ts | 33 +++++---- .../utils/resolve-full-name.utils.ts | 9 ++- .../utils/resolve-shifts-id.utils.ts | 20 +++-- .../utils/selects.utils.ts | 30 -------- src/time-and-attendance/utils/type.utils.ts | 71 ------------------ targo-backend@0.0.1 | 0 tsx | 0 20 files changed, 205 insertions(+), 214 deletions(-) delete mode 100644 npm delete mode 100644 targo-backend@0.0.1 delete mode 100644 tsx diff --git a/npm b/npm deleted file mode 100644 index e69de29..0000000 diff --git a/src/common/errors/result-error.factory.ts b/src/common/errors/result-error.factory.ts index b498545..0cf318a 100644 --- a/src/common/errors/result-error.factory.ts +++ b/src/common/errors/result-error.factory.ts @@ -2,11 +2,11 @@ export type Result = | { success: true; data: T } | { success: false; error: E }; -const success = (data: T): Result => { - return { success: true, data }; -} +// const success = (data: T): Result => { +// return { success: true, data }; +// } -const failure = (error: E): Result => { - return { success: false, error }; -} +// const failure = (error: E): Result => { +// return { success: false, error }; +// } diff --git a/src/time-and-attendance/domains/services/overtime.service.ts b/src/time-and-attendance/domains/services/overtime.service.ts index e43c4d3..acff2c4 100644 --- a/src/time-and-attendance/domains/services/overtime.service.ts +++ b/src/time-and-attendance/domains/services/overtime.service.ts @@ -1,10 +1,28 @@ import { Injectable, Logger } from '@nestjs/common'; +import { Prisma, PrismaClient } from '@prisma/client'; import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils'; import { PrismaService } from 'src/prisma/prisma.service'; import { DAILY_LIMIT_HOURS, WEEKLY_LIMIT_HOURS } from 'src/time-and-attendance/utils/constants.utils'; -import { Tx, WeekOvertimeSummary } from 'src/time-and-attendance/utils/type.utils'; +type Tx = Prisma.TransactionClient | PrismaClient; + +type WeekOvertimeSummary = { + week_start:string; + week_end: string; + week_total_hours: number; + weekly_overtime: number; + daily_overtime_kept: number; + total_overtime: number; + breakdown: Array<{ + date:string; + day_hours: number; + day_overtime: number; + daily_kept: number; + running_total_before: number; + }>; +}; + @Injectable() export class OvertimeService { diff --git a/src/time-and-attendance/expenses/controllers/expense.controller.ts b/src/time-and-attendance/expenses/controllers/expense.controller.ts index fe37b0e..88357c1 100644 --- a/src/time-and-attendance/expenses/controllers/expense.controller.ts +++ b/src/time-and-attendance/expenses/controllers/expense.controller.ts @@ -1,5 +1,4 @@ import { Controller, Post, Param, Body, Patch, Delete, Req, UnauthorizedException } from "@nestjs/common"; -import { CreateExpenseResult } from "src/time-and-attendance/utils/type.utils"; import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service"; import { ExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-create.dto"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; diff --git a/src/time-and-attendance/expenses/services/expense-upsert.service.ts b/src/time-and-attendance/expenses/services/expense-upsert.service.ts index 6a52f76..19be304 100644 --- a/src/time-and-attendance/expenses/services/expense-upsert.service.ts +++ b/src/time-and-attendance/expenses/services/expense-upsert.service.ts @@ -1,6 +1,6 @@ import { toDateFromString, toStringFromDate, weekStartSunday } from "src/time-and-attendance/utils/date-time.utils"; import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; -import { NormalizedExpense } from "src/time-and-attendance/utils/type.utils"; +// import { NormalizedExpense } from "src/time-and-attendance/utils/type.utils"; import { expense_select } from "src/time-and-attendance/utils/selects.utils"; import { PrismaService } from "src/prisma/prisma.service"; import { GetExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-get.dto"; @@ -8,6 +8,7 @@ import { ExpenseEntity } from "src/time-and-attendance/expenses/dtos/expense-ent import { ExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-create.dto"; import { Injectable } from "@nestjs/common"; import { Result } from "src/common/errors/result-error.factory"; +import { NormalizedExpense } from "src/time-and-attendance/utils/type.utils"; @Injectable() diff --git a/src/time-and-attendance/leave-requests/mappers/leave-requests.mapper.ts b/src/time-and-attendance/leave-requests/mappers/leave-requests.mapper.ts index 9f823fc..ad772a0 100644 --- a/src/time-and-attendance/leave-requests/mappers/leave-requests.mapper.ts +++ b/src/time-and-attendance/leave-requests/mappers/leave-requests.mapper.ts @@ -1,6 +1,8 @@ import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; -import { LeaveRequestRow } from "src/time-and-attendance/utils/type.utils"; import { Prisma } from "@prisma/client"; +import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; + +type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>; const toNum = (value?: Prisma.Decimal | null) => value !== null && value !== undefined ? Number(value) : undefined; diff --git a/src/time-and-attendance/leave-requests/utils/leave-request.transform.ts b/src/time-and-attendance/leave-requests/utils/leave-request.transform.ts index 5ff49fd..47f76dc 100644 --- a/src/time-and-attendance/leave-requests/utils/leave-request.transform.ts +++ b/src/time-and-attendance/leave-requests/utils/leave-request.transform.ts @@ -1,9 +1,11 @@ +import { Prisma } from "@prisma/client"; import { LeaveRequestViewDto } from "src/time-and-attendance/leave-requests/dtos/leave-request-view.dto"; import { mapArchiveRowToView } from "src/time-and-attendance/leave-requests/mappers/leave-requests-archive.mapper"; import { mapRowToView } from "src/time-and-attendance/leave-requests/mappers/leave-requests.mapper"; import { LeaveRequestArchiveRow } from "src/time-and-attendance/leave-requests/utils/leave-requests-archive.select"; -import { LeaveRequestRow } from "src/time-and-attendance/utils/type.utils"; +import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; +export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>; /** Active (table leave_requests) : proxy to base mapper */ export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto { diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts index fa1d7b9..b15765f 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service.ts @@ -1,7 +1,6 @@ import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils"; import { PrismaService } from "src/prisma/prisma.service"; import { Injectable } from "@nestjs/common"; -import { Prisma } from "@prisma/client"; import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; import { Result } from "src/common/errors/result-error.factory"; diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts index f7d2a5c..c55f2db 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts @@ -1,5 +1,4 @@ import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common"; -import { CreatePresetResult, DeletePresetResult, UpdatePresetResult } from "src/time-and-attendance/utils/type.utils"; import { Prisma, Weekday } from "@prisma/client"; import { toDateFromString, toHHmmFromDate } from "src/time-and-attendance/utils/date-time.utils"; import { PrismaService } from "src/prisma/prisma.service"; @@ -21,7 +20,7 @@ export class SchedulePresetsUpsertService { async createPreset(email: string, dto: SchedulePresetsDto): Promise> { try { const shifts_data = await this.normalizePresetShifts(dto); - if (!shifts_data) return { success: false, error: `Employee with email: ${email} or dto not found` }; + if (!shifts_data.success) return { success: false, error: `Employee with email: ${email} or dto not found` }; const employee_id = await this.emailResolver.findIdByEmail(email); if (!employee_id.success) return { success: false, error: employee_id.error }; @@ -38,7 +37,7 @@ export class SchedulePresetsUpsertService { employee_id: employee_id.data, name: dto.name, is_default: !!dto.is_default, - shifts: { create: shifts_data }, + shifts: { create: shifts_data.data }, }, }); return { success: true, data: created } @@ -66,6 +65,7 @@ export class SchedulePresetsUpsertService { if (!existing) return { success: false, error: `Preset "${dto.name}" not found` }; const shifts_data = await this.normalizePresetShifts(dto); + if(!shifts_data.success) return { success: false, error: 'An error occured during normalization'} await this.prisma.$transaction(async (tx) => { if (typeof dto.is_default === 'boolean') { @@ -87,33 +87,34 @@ export class SchedulePresetsUpsertService { }, }); } - if (shifts_data.length <= 0) return { success: false, error: 'Preset shifts to update not found' }; + if (shifts_data.data.length <= 0) return { success: false, error: 'Preset shifts to update not found' }; await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } }); - try { - const create_many_data: Prisma.SchedulePresetShiftsCreateManyInput[] = - shifts_data.map((shift) => { - if (!shift.bank_code || !('connect' in shift.bank_code) || typeof shift.bank_code.connect?.id !== 'number') { - throw new NotFoundException(`Bank code is required for updates( ${shift.week_day}, ${shift.sort_order})`); - } - const bank_code_id = shift.bank_code.connect.id; - return { - preset_id: existing.id, - week_day: shift.week_day, - sort_order: shift.sort_order, - start_time: shift.start_time, - end_time: shift.end_time, - is_remote: shift.is_remote ?? false, - bank_code_id: bank_code_id, - }; - }); - await tx.schedulePresetShifts.createMany({ data: create_many_data }); - - return { success: true, data: create_many_data } - } catch (error) { - return { success: false, error: 'An error occured. Invalid data detected. ' }; - } + // try { + // const create_many_data: Result = + // shifts_data.data.map((shift) => { + // if (!shift.bank_code || !('connect' in shift.bank_code) || typeof shift.bank_code.connect?.id !== 'number') { + // return { success: false, error: `Bank code is required for updates( ${shift.week_day}, ${shift.sort_order})`} + // } + // const bank_code_id = shift.bank_code.connect.id; + // return { + // preset_id: existing.id, + // week_day: shift.week_day, + // sort_order: shift.sort_order, + // start_time: shift.start_time, + // end_time: shift.end_time, + // is_remote: shift.is_remote ?? false, + // bank_code_id: bank_code_id, + // }; + // }); + // if(!create_many_data.success) return { success: false, error: 'Invalid data'} + // await tx.schedulePresetShifts.createMany({ data: create_many_data.data }); + + // return { success: true, data: create_many_data } + // } catch (error) { + // return { success: false, error: 'An error occured. Invalid data detected. ' }; + // } }); const saved = await this.prisma.schedulePresets.findUnique({ @@ -175,15 +176,16 @@ export class SchedulePresetsUpsertService { //resolve bank_code_id using type and convert hours to TIME and valid shifts end/start private async normalizePresetShifts( dto: SchedulePresetsDto - ): Promise { + ): Promise> { if (!dto.preset_shifts?.length) throw new NotFoundException(`Empty or preset shifts not found`); const types = Array.from(new Set(dto.preset_shifts.map((shift) => shift.type))); const bank_code_set = new Map(); for (const type of types) { - const { id } = await this.typeResolver.findIdAndModifierByType(type); - bank_code_set.set(type, id) + const bank_code = await this.typeResolver.findIdAndModifierByType(type); + if (!bank_code.success) return { success: false, error: 'Bank_code not found' } + bank_code_set.set(type, bank_code.data.id); } const pair_set = new Set(); @@ -216,6 +218,6 @@ export class SchedulePresetsUpsertService { is_remote: !!shift.is_remote, }; }); - return items; + return { success: true, data: items }; } } diff --git a/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts b/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts index 6028dd5..789117f 100644 --- a/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts +++ b/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts @@ -1,6 +1,5 @@ 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 { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes"; import { Result } from "src/common/errors/result-error.factory"; diff --git a/src/time-and-attendance/time-tracker/shifts/services/shifts-create.service.ts b/src/time-and-attendance/time-tracker/shifts/services/shifts-create.service.ts index 94cdd44..76c7bdf 100644 --- a/src/time-and-attendance/time-tracker/shifts/services/shifts-create.service.ts +++ b/src/time-and-attendance/time-tracker/shifts/services/shifts-create.service.ts @@ -63,12 +63,13 @@ export class ShiftsCreateService { try { //transform string format to date and HHmm const normed_shift = await this.normalizeShiftDto(dto); - if (normed_shift.end_time <= normed_shift.start_time) return { + if(!normed_shift.success) return { success: false, error: 'An error occured during normalization' } + if (normed_shift.data.end_time <= normed_shift.data.start_time) return { success: false, error: `INVALID_SHIFT - ` - + `start_time: ${toStringFromHHmm(normed_shift.start_time)},` - + `end_time: ${toStringFromHHmm(normed_shift.end_time)},` - + `date: ${toStringFromDate(normed_shift.date)}.` + + `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 const timesheet = await this.prisma.timesheets.findUnique({ @@ -78,16 +79,17 @@ export class ShiftsCreateService { if (!timesheet) return { success: false, error: `INVALID_TIMESHEET -` - + `start_time: ${toStringFromHHmm(normed_shift.start_time)},` - + `end_time: ${toStringFromHHmm(normed_shift.end_time)},` - + `date: ${toStringFromDate(normed_shift.date)}.` + + `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 - const bank_code = 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 }; //fetchs existing shifts from DB to check for overlaps const existing_shifts = await this.prisma.shifts.findMany({ - where: { timesheet_id: timesheet.id, date: normed_shift.date }, + where: { timesheet_id: timesheet.id, date: normed_shift.data.date }, select: { id: true, date: true, start_time: true, end_time: true }, }); for (const existing of existing_shifts) { @@ -96,16 +98,16 @@ export class ShiftsCreateService { const existing_date = await toDateFromString(existing.date); const has_overlap = overlaps( - { start: normed_shift.start_time, end: normed_shift.end_time, date: normed_shift.date }, + { start: normed_shift.data.start_time, end: normed_shift.data.end_time, date: normed_shift.data.date }, { start: existing_start, end: existing_end, date: existing_date }, ); if (has_overlap) { return { success: false, error: `SHIFT_OVERLAP` - + `new shift: ${toStringFromHHmm(normed_shift.start_time)}–${toStringFromHHmm(normed_shift.end_time)} ` + + `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.date)})`, + + `date: ${toStringFromDate(normed_shift.data.date)})`, } } } @@ -114,10 +116,10 @@ export class ShiftsCreateService { const created_shift = await this.prisma.shifts.create({ data: { timesheet_id: timesheet.id, - bank_code_id: bank_code.id, - date: normed_shift.date, - start_time: normed_shift.start_time, - end_time: normed_shift.end_time, + bank_code_id: bank_code_id.data, + date: normed_shift.data.date, + start_time: normed_shift.data.start_time, + end_time: normed_shift.data.end_time, is_approved: dto.is_approved, is_remote: dto.is_remote, comment: dto.comment ?? '', @@ -145,11 +147,14 @@ export class ShiftsCreateService { // LOCAL HELPERS //_________________________________________________________________ //converts all string hours and date to Date and HHmm formats - private normalizeShiftDto = async (dto: ShiftDto): Promise => { - const { id: bank_code_id } = await this.typeResolver.findBankCodeIDByType(dto.type); + private normalizeShiftDto = async (dto: ShiftDto): Promise> => { + const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type); + if(!bank_code_id.success) return { success: false, error: 'Bank_code not found'} + const date = toDateFromString(dto.date); const start_time = toHHmmFromString(dto.start_time); const end_time = toHHmmFromString(dto.end_time); - return { date, start_time, end_time, bank_code_id: bank_code_id }; + + return { success: true, data: {date, start_time, end_time, bank_code_id: bank_code_id.data} }; } } diff --git a/src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service.ts b/src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service.ts index 3757ae6..100b080 100644 --- a/src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service.ts +++ b/src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service.ts @@ -1,14 +1,10 @@ -import { BadRequestException, NotFoundException, ConflictException } from "@nestjs/common"; import { Result } from "src/common/errors/result-error.factory"; import { PrismaService } from "src/prisma/prisma.service"; import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto"; -import { ShiftEntity } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-entity.dto"; -import { GetShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto"; -import { ShiftsCreateService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-create.service"; -import { toDateFromString, toHHmmFromString, overlaps, toStringFromHHmm, toStringFromDate } from "src/time-and-attendance/utils/date-time.utils"; +import { toDateFromString, toHHmmFromString, toStringFromHHmm, toStringFromDate, overlaps } from "src/time-and-attendance/utils/date-time.utils"; import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; import { shift_select } from "src/time-and-attendance/utils/selects.utils"; -import { UpdateShiftResult, Normalized } from "src/time-and-attendance/utils/type.utils"; +import { Normalized } from "src/time-and-attendance/utils/type.utils"; export class ShiftsUpdateDeleteService { constructor( @@ -22,7 +18,7 @@ export class ShiftsUpdateDeleteService { if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' }; //calls the update functions and await the return of successfull result or not - const results = await Promise.allSettled(shifts.map(shift => this.updateShifts(shift))); + const results = await Promise.allSettled(shifts.map(shift => this.updateShift(shift))); //return arrays of updated shifts or errors const updated_shifts: ShiftDto[] = []; @@ -52,7 +48,7 @@ export class ShiftsUpdateDeleteService { //_________________________________________________________________ // UPDATE //_________________________________________________________________ - async updateShifts(dto: ShiftDto): Promise> { + async updateShift(dto: ShiftDto): Promise> { try { //finds original shift const original = await this.prisma.shifts.findFirst({ @@ -63,32 +59,36 @@ export class ShiftsUpdateDeleteService { //transform string format to date and HHmm const normed_shift = await this.normalizeShiftDto(dto); - if (normed_shift.end_time <= normed_shift.start_time) return { + if (!normed_shift.success) return { success: false, error: 'An error occured during normalization' } + if (normed_shift.data.end_time <= normed_shift.data.start_time) return { success: false, error: `INVALID_SHIFT - ` - + `start_time: ${toStringFromHHmm(normed_shift.start_time)},` - + `end_time: ${toStringFromHHmm(normed_shift.end_time)},` - + `date: ${toStringFromDate(normed_shift.date)}.` + + `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},` + + `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},` + + `date: ${toStringFromDate(normed_shift.data.date)}.` }; - + const overlap_check = await this.overlapChecker(normed_shift.data); + if(!overlap_check.success) return { success: false, error: 'Invalid shift, overlaps with existing shifts'} + //finds bank_code_id using the type const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type); + if (!bank_code.success) return { success: false, error: 'No bank_code_id found' }; //updates sent to DB const updated = await this.prisma.shifts.update({ where: { id: original.id }, data: { - date: normed_shift.date, - start_time: normed_shift.start_time, - end_time: normed_shift.end_time, - bank_code_id: bank_code.id, + date: normed_shift.data.date, + start_time: normed_shift.data.start_time, + end_time: normed_shift.data.end_time, + bank_code_id: bank_code.data, comment: dto.comment, is_approved: dto.is_approved, is_remote: dto.is_remote, }, select: shift_select, }); - if(!updated) return {success: false, error: ' An error occured during update, Invalid Datas'}; + if (!updated) return { success: false, error: ' An error occured during update, Invalid Datas' }; // builds an object to return for display in the frontend const shift: ShiftDto = { @@ -134,11 +134,42 @@ export class ShiftsUpdateDeleteService { //_________________________________________________________________ // helpers //_________________________________________________________________ - private normalizeShiftDto = async (dto: ShiftDto): Promise => { - const { id: bank_code_id } = await this.typeResolver.findBankCodeIDByType(dto.type); + private normalizeShiftDto = async (dto: ShiftDto): Promise> => { + const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type); + if (!bank_code_id.success) return { success: false, error: 'Bank_code not found' } + const date = toDateFromString(dto.date); const start_time = toHHmmFromString(dto.start_time); const end_time = toHHmmFromString(dto.end_time); - return { date, start_time, end_time, bank_code_id: bank_code_id }; + + return { success: true, data: { date, start_time, end_time, bank_code_id: bank_code_id.data } }; + } + + private overlapChecker = async (dto: Normalized): Promise> => { + + const existing_shifts = await this.prisma.shifts.findMany({ + where: { date: dto.date }, + select: { id: true, date: true, start_time: true, end_time: true }, + }); + for (const existing of existing_shifts) { + 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: dto.start_time, end: dto.end_time, date: dto.date }, + { start: existing_start, end: existing_end, date: existing_date }, + ); + if (has_overlap) { + return { + success: false, + error: `SHIFT_OVERLAP` + + `new shift: ${toStringFromHHmm(dto.start_time)}–${toStringFromHHmm(dto.end_time)} ` + + `existing shift: ${toStringFromHHmm(existing.start_time)}–${toStringFromHHmm(existing.end_time)} ` + + `date: ${toStringFromDate(dto.date)})`, + } + } + } + return { success: true, data: undefined } } } \ No newline at end of file diff --git a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts index 2d8c5ae..b54ea1e 100644 --- a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts +++ b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service.ts @@ -1,12 +1,28 @@ import { sevenDaysFrom, toStringFromDate, toHHmmFromDate, toDateFromString } from "src/time-and-attendance/utils/date-time.utils"; import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/time-and-attendance/utils/constants.utils"; -import { Injectable, NotFoundException } from "@nestjs/common"; -import { TotalExpenses, TotalHours } from "src/time-and-attendance/utils/type.utils"; +import { Injectable } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; import { Timesheets } from "src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto"; import { Result } from "src/common/errors/result-error.factory"; +export type TotalHours = { + regular: number; + evening: number; + emergency: number; + overtime: number; + vacation: number; + holiday: number; + sick: number; +}; + +export type TotalExpenses = { + expenses: number; + per_diem: number; + on_call: number; + mileage: number; +}; + @Injectable() export class GetTimesheetsOverviewService { constructor( @@ -61,14 +77,14 @@ export class GetTimesheetsOverviewService { const employee_fullname = `${user.first_name} ${user.last_name}`.trim(); //maps all timesheet's infos - const timesheets = rows.map((timesheet) => this.mapOneTimesheet(timesheet)); - return { success: true, data: { employee_fullname, timesheets } }; + const timesheets = await Promise.all(rows.map((timesheet) => this.mapOneTimesheet(timesheet))); + + return { success: true, data:{ employee_fullname, timesheets} }; } catch (error) { return { success: false, error} } } - //----------------------------------------------------------------------------------- // MAPPERS & HELPERS //----------------------------------------------------------------------------------- @@ -111,11 +127,14 @@ export class GetTimesheetsOverviewService { const weekly_hours: TotalHours[] = [emptyHours()]; const weekly_expenses: TotalExpenses[] = [emptyExpenses()]; + + //map of days const days = day_dates.map((date) => { const date_iso = toStringFromDate(date); const shifts_source = shifts_by_date.get(date_iso) ?? []; const expenses_source = expenses_by_date.get(date_iso) ?? []; + //inner map of shifts const shifts = shifts_source.map((shift) => ({ timesheet_id: shift.timesheet_id, @@ -139,6 +158,7 @@ export class GetTimesheetsOverviewService { is_approved: expense.is_approved ?? false, comment: expense.comment ?? '', supervisor_comment: expense.supervisor_comment, + type: expense.type, })); //daily totals diff --git a/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts b/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts index 017c49f..725a84f 100644 --- a/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts +++ b/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts @@ -1,44 +1,47 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { Prisma, PrismaClient } from "@prisma/client"; +import { Result } from "src/common/errors/result-error.factory"; import { PrismaService } from "src/prisma/prisma.service"; type Tx = Prisma.TransactionClient | PrismaClient; @Injectable() export class BankCodesResolver { - constructor(private readonly prisma: PrismaService) {} + constructor(private readonly prisma: PrismaService) { } //find id and modifier by type - readonly findIdAndModifierByType = async ( type: string, client?: Tx - ): Promise<{id:number; modifier: number }> => { + readonly findIdAndModifierByType = async (type: string, client?: Tx + ): Promise> => { const db = client ?? this.prisma; const bank = await db.bankCodes.findFirst({ where: { type }, select: { id: true, modifier: true }, }); + if (!bank) return { success: false, error: `Unknown bank code type: ${type}` }; - if(!bank) throw new NotFoundException(`Unknown bank code type: ${type}`); - return { id: bank.id, modifier: bank.modifier }; + return { success: true, data: { id: bank.id, modifier: bank.modifier } }; }; //finds only id by type - readonly findBankCodeIDByType = async (type: string, client?: Tx) => { + readonly findBankCodeIDByType = async (type: string, client?: Tx): Promise> => { const db = client ?? this.prisma; - const bank_code_id = await db.bankCodes.findFirst({ + const bank_code = await db.bankCodes.findFirst({ where: { type }, - select: {id: true}, + select: { id: true }, }); - if(!bank_code_id) throw new NotFoundException(`Unkown bank type: ${type}`); - return bank_code_id; + if (!bank_code) return { success: false, error:`Unkown bank type: ${type}`}; + + return { success: true, data: bank_code.id}; } - - readonly findTypeByBankCodeId = async (bank_code_id: number, client?: Tx) => { + + readonly findTypeByBankCodeId = async (bank_code_id: number, client?: Tx): Promise> => { const db = client ?? this.prisma; - const type = await db.bankCodes.findFirst({ + const bank_code = await db.bankCodes.findFirst({ where: { id: bank_code_id }, select: { type: true }, }); - if(!type) throw new NotFoundException(`Type with id : ${bank_code_id} not found`); - return type; + if (!bank_code) return {success: false, error: `Type with id : ${bank_code_id} not found` } + + return {success: true, data: bank_code.type}; } } \ No newline at end of file diff --git a/src/time-and-attendance/utils/resolve-full-name.utils.ts b/src/time-and-attendance/utils/resolve-full-name.utils.ts index ef6669b..ebb3df5 100644 --- a/src/time-and-attendance/utils/resolve-full-name.utils.ts +++ b/src/time-and-attendance/utils/resolve-full-name.utils.ts @@ -1,5 +1,6 @@ -import { Injectable, NotFoundException } from "@nestjs/common"; +import { Injectable } from "@nestjs/common"; import { Prisma, PrismaClient } from "@prisma/client"; +import { Result } from "src/common/errors/result-error.factory"; import { PrismaService } from "src/prisma/prisma.service"; type Tx = Prisma.TransactionClient | PrismaClient; @@ -8,15 +9,15 @@ type Tx = Prisma.TransactionClient | PrismaClient; export class FullNameResolver { constructor(private readonly prisma: PrismaService){} - readonly resolveFullName = async (employee_id: number, client?: Tx): Promise =>{ + readonly resolveFullName = async (employee_id: number, client?: Tx): Promise> =>{ const db = client ?? this.prisma; const employee = await db.employees.findUnique({ where: { id: employee_id }, select: { user: { select: {first_name: true, last_name: true} } }, }); - if(!employee) throw new NotFoundException(`Unknown user with name: ${employee_id}`) + if(!employee) return { success: false, error: `Unknown user with id ${employee_id}`} const full_name = ( employee.user.first_name + " " + employee.user.last_name ) || " "; - return full_name; + return {success: true, data: full_name }; } } \ No newline at end of file diff --git a/src/time-and-attendance/utils/resolve-shifts-id.utils.ts b/src/time-and-attendance/utils/resolve-shifts-id.utils.ts index e76d144..350069a 100644 --- a/src/time-and-attendance/utils/resolve-shifts-id.utils.ts +++ b/src/time-and-attendance/utils/resolve-shifts-id.utils.ts @@ -1,14 +1,23 @@ import { Prisma, PrismaClient } from "@prisma/client"; -import { NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { ShiftKey } from "src/time-and-attendance/utils/type.utils"; +import { Result } from "src/common/errors/result-error.factory"; type Tx = Prisma.TransactionClient | PrismaClient; +interface ShiftKey { + timesheet_id: number; + date: Date; + start_time: Date; + end_time: Date; + bank_code_id: number; + is_remote: boolean; + comment?: string | null; +} + export class ShiftIdResolver { constructor(private readonly prisma: PrismaService) {} - readonly findShiftIdByData = async ( key: ShiftKey, client?: Tx ): Promise<{id:number}> => { + readonly findShiftIdByData = async ( key: ShiftKey, client?: Tx ): Promise> => { const db = client ?? this.prisma; const shift = await db.shifts.findFirst({ where: { @@ -23,7 +32,8 @@ export class ShiftIdResolver { select: { id: true }, }); - if(!shift) throw new NotFoundException(`shift not found`); - return { id: shift.id }; + if(!shift) return { success: false, error: `shift not found`} + + return { success: true, data: shift.id }; }; } \ No newline at end of file diff --git a/src/time-and-attendance/utils/selects.utils.ts b/src/time-and-attendance/utils/selects.utils.ts index 98a27ec..7938519 100644 --- a/src/time-and-attendance/utils/selects.utils.ts +++ b/src/time-and-attendance/utils/selects.utils.ts @@ -51,36 +51,6 @@ export const leaveRequestsSelect = { }, } satisfies Prisma.LeaveRequestsSelect; - -export const EXPENSE_SELECT = { - date: true, - amount: true, - mileage: true, - comment: true, - is_approved: true, - supervisor_comment: true, - bank_code: { select: { type: true } }, -} as const; - -export const EXPENSE_ASC_ORDER = { date: 'asc' as const }; - -export const PAY_PERIOD_SELECT = { - period_start: true, - period_end: true, -} as const; - -export const SHIFT_SELECT = { - date: true, - start_time: true, - end_time: true, - comment: true, - is_approved: true, - is_remote: true, - bank_code: {select: { type: true } }, -} as const; - -export const SHIFT_ASC_ORDER = [{date: 'asc' as const}, {start_time: 'asc' as const}]; - export const timesheet_select = { id: true, employee_id: true, diff --git a/src/time-and-attendance/utils/type.utils.ts b/src/time-and-attendance/utils/type.utils.ts index b38ecc8..959c6c8 100644 --- a/src/time-and-attendance/utils/type.utils.ts +++ b/src/time-and-attendance/utils/type.utils.ts @@ -1,47 +1,9 @@ -import { Prisma, PrismaClient } from "@prisma/client"; -import { GetExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-get.dto"; -import { SchedulePresetsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; -import { GetShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto"; -import { ShiftEntity } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-entity.dto"; -import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; - - -export type TotalHours = { - regular: number; - evening: number; - emergency: number; - overtime: number; - vacation: number; - holiday: number; - sick: number; -}; - -export type TotalExpenses = { - expenses: number; - per_diem: number; - on_call: number; - mileage: number; -}; - export type Normalized = { date: Date; start_time: Date; end_time: Date; bank_code_id: number; }; -export type CreateShiftResult = { ok: true; data: GetShiftDto } | { ok: false; error: any }; -export type UpdateShiftResult = { ok: true; id: number; data: GetShiftDto } | { ok: false; id: number; error: any }; -export type NormedOk = { - index: number; - dto: ShiftEntity; - normed: Normalized; - timesheet_id: number; -}; - -export type DeletePresetResult = { ok: true; id: number; } | { ok: false; id: number; error: any }; -export type CreatePresetResult = { ok: true; } | { ok: false; error: any }; -export type UpdatePresetResult = { ok: true; id: number; data: SchedulePresetsDto } | { ok: false; id: number; error: any }; - export type NormalizedExpense = { date: Date; @@ -51,9 +13,6 @@ export type NormalizedExpense = { parsed_mileage?: number; parsed_attachment?: number; }; -export type CreateExpenseResult = { ok: true; data: GetExpenseDto } | { ok: false; error: any }; -export type DeleteExpenseResult = { ok: true; id: number; } | { ok: false; id: number; error: any }; - export type ShiftResponse = { week_day: string; @@ -76,33 +35,3 @@ export type ApplyResult = { created: number; skipped: number; } - -export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>; - -export type Tx = Prisma.TransactionClient | PrismaClient; - -export type WeekOvertimeSummary = { - week_start:string; - week_end: string; - week_total_hours: number; - weekly_overtime: number; - daily_overtime_kept: number; - total_overtime: number; - breakdown: Array<{ - date:string; - day_hours: number; - day_overtime: number; - daily_kept: number; - running_total_before: number; - }>; -}; - -export interface ShiftKey { - timesheet_id: number; - date: Date; - start_time: Date; - end_time: Date; - bank_code_id: number; - is_remote: boolean; - comment?: string | null; -} \ No newline at end of file diff --git a/targo-backend@0.0.1 b/targo-backend@0.0.1 deleted file mode 100644 index e69de29..0000000 diff --git a/tsx b/tsx deleted file mode 100644 index e69de29..0000000