feat(Result): ajusted return values to match Result pattern.
This commit is contained in:
parent
a8d53ab0aa
commit
1d9eaeab30
|
|
@ -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 };
|
||||
// }
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
// 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. ' };
|
||||
}
|
||||
// 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 };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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} };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,25 +59,29 @@ 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,
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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;
|
||||
|
|
@ -10,35 +11,37 @@ export class BankCodesResolver {
|
|||
|
||||
//find id and modifier by type
|
||||
readonly findIdAndModifierByType = async (type: string, client?: Tx
|
||||
): Promise<{id:number; modifier: number }> => {
|
||||
): 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 },
|
||||
});
|
||||
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};
|
||||
}
|
||||
}
|
||||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
@ -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 };
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user