feat(Result): ajusted return values to match Result pattern.

This commit is contained in:
Matthieu Haineault 2025-11-12 09:16:37 -05:00
parent a8d53ab0aa
commit 1d9eaeab30
20 changed files with 205 additions and 214 deletions

0
npm
View File

View File

@ -2,11 +2,11 @@ export type Result<T, E> =
| { success: true; data: T }
| { success: false; error: E };
const success = <T>(data: T): Result<T, never> => {
return { success: true, data };
}
// const success = <T>(data: T): Result<T, never> => {
// return { success: true, data };
// }
const failure = <E>(error: E): Result<never, E> => {
return { success: false, error };
}
// const failure = <E>(error: E): Result<never, E> => {
// return { success: false, error };
// }

View File

@ -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 {

View File

@ -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";

View File

@ -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()

View File

@ -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;

View File

@ -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 {

View File

@ -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";

View File

@ -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<Result<SchedulePresetsDto, string>> {
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<Prisma.SchedulePresetShiftsCreateManyInput[], string> =
// 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<Prisma.SchedulePresetShiftsCreateWithoutPresetInput[]> {
): Promise<Result<Prisma.SchedulePresetShiftsCreateWithoutPresetInput[], string>> {
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<string, number>();
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<string>();
@ -216,6 +218,6 @@ export class SchedulePresetsUpsertService {
is_remote: !!shift.is_remote,
};
});
return items;
return { success: true, data: items };
}
}

View File

@ -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";

View File

@ -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<Normalized> => {
const { id: bank_code_id } = await this.typeResolver.findBankCodeIDByType(dto.type);
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
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} };
}
}

View File

@ -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<Result<ShiftDto, string>> {
async updateShift(dto: ShiftDto): Promise<Result<ShiftDto, string>> {
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<Normalized> => {
const { id: bank_code_id } = await this.typeResolver.findBankCodeIDByType(dto.type);
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
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<Result<void, string>> => {
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 }
}
}

View File

@ -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

View File

@ -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<Result<{ id: number; modifier: number }, string>> => {
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<Result<number, string>> => {
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<Result<string, string>> => {
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};
}
}

View File

@ -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<string> =>{
readonly resolveFullName = async (employee_id: number, client?: Tx): Promise<Result<string, string>> =>{
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 };
}
}

View File

@ -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<Result<number, string>> => {
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 };
};
}

View File

@ -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,

View File

@ -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;
}

View File

0
tsx
View File