This commit is contained in:
Nicolas 2025-10-30 15:00:38 -04:00
commit e8bd0403ea
21 changed files with 264 additions and 224 deletions

View File

@ -454,24 +454,16 @@
] ]
} }
}, },
"/schedule-presets/{email}": { "/schedule-presets/create/{employee_id}": {
"put": { "post": {
"operationId": "SchedulePresetsController_upsert", "operationId": "SchedulePresetsController_createPreset",
"parameters": [ "parameters": [
{ {
"name": "email", "name": "employee_id",
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"type": "string" "type": "number"
}
},
{
"name": "action",
"required": true,
"in": "query",
"schema": {
"type": "string"
} }
} }
], ],
@ -485,6 +477,39 @@
} }
} }
}, },
"responses": {
"201": {
"description": ""
}
},
"tags": [
"SchedulePresets"
]
}
},
"/schedule-presets/update/{preset_id}": {
"patch": {
"operationId": "SchedulePresetsController_updatePreset",
"parameters": [
{
"name": "preset_id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SchedulePresetsUpdateDto"
}
}
}
},
"responses": { "responses": {
"200": { "200": {
"description": "" "description": ""
@ -493,16 +518,18 @@
"tags": [ "tags": [
"SchedulePresets" "SchedulePresets"
] ]
}
}, },
"get": { "/schedule-presets/delete/{preset_id}": {
"operationId": "SchedulePresetsController_findListByEmail", "delete": {
"operationId": "SchedulePresetsController_deletePreset",
"parameters": [ "parameters": [
{ {
"name": "email", "name": "preset_id",
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"type": "string" "type": "number"
} }
} }
], ],
@ -516,16 +543,39 @@
] ]
} }
}, },
"/schedule-presets/apply-presets/{email}": { "/schedule-presets/find/{employee_id}": {
"get": {
"operationId": "SchedulePresetsController_findListById",
"parameters": [
{
"name": "employee_id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"SchedulePresets"
]
}
},
"/schedule-presets/apply-presets/{employee_id}": {
"post": { "post": {
"operationId": "SchedulePresetsController_applyPresets", "operationId": "SchedulePresetsController_applyPresets",
"parameters": [ "parameters": [
{ {
"name": "email", "name": "employee_id",
"required": true, "required": true,
"in": "path", "in": "path",
"schema": { "schema": {
"type": "string" "type": "number"
} }
}, },
{ {
@ -853,6 +903,10 @@
"type": "object", "type": "object",
"properties": {} "properties": {}
}, },
"SchedulePresetsUpdateDto": {
"type": "object",
"properties": {}
},
"ExpenseDto": { "ExpenseDto": {
"type": "object", "type": "object",
"properties": {} "properties": {}

View File

@ -1,6 +1,5 @@
import { BadRequestException, Injectable, Logger } from "@nestjs/common"; import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "../../../prisma/prisma.service"; import { PrismaService } from '../../../prisma/prisma.service';
import { Decimal } from "@prisma/client/runtime/library";
@Injectable() @Injectable()
export class MileageService { export class MileageService {

View File

@ -2,16 +2,11 @@
import { BadRequestException, Injectable } from "@nestjs/common"; import { BadRequestException, Injectable } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { LeaveTypes } from "@prisma/client"; import { LeaveTypes } from "@prisma/client";
// import { toDateOnly, toStringFromDate } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers";
import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types";
import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time.utils"; import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time.utils";
// import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service";
@Injectable() @Injectable()
export class LeaveRequestsUtils { export class LeaveRequestsUtils {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
// private readonly shiftsService: ShiftsUpsertService,
){} ){}
async syncShift( async syncShift(
@ -47,7 +42,6 @@ export class LeaveRequestsUtils {
include: { bank_code: true }, include: { bank_code: true },
}); });
const action: UpsertAction = existing ? 'update' : 'create';
// await this.shiftsService.upsertShifts(email, action, { // await this.shiftsService.upsertShifts(email, action, {
// old_shift: existing // old_shift: existing

View File

@ -1,9 +1,9 @@
import { Module } from "@nestjs/common";
import { EmailToIdResolver } from "./utils/resolve-email-id.utils";
import { EmployeeTimesheetResolver } from "./utils/resolve-timesheet.utils"; import { EmployeeTimesheetResolver } from "./utils/resolve-timesheet.utils";
import { FullNameResolver } from "./utils/resolve-full-name.utils"; import { EmailToIdResolver } from "./utils/resolve-email-id.utils";
import { BankCodesResolver } from "./utils/resolve-bank-type-id.utils"; import { BankCodesResolver } from "./utils/resolve-bank-type-id.utils";
import { FullNameResolver } from "./utils/resolve-full-name.utils";
import { PrismaModule } from "src/prisma/prisma.module"; import { PrismaModule } from "src/prisma/prisma.module";
import { Module } from "@nestjs/common";
@Module({ @Module({
imports: [PrismaModule], imports: [PrismaModule],

View File

@ -1 +0,0 @@
export type UpsertAction = 'create' | 'update' | 'delete';

View File

@ -1,44 +1,63 @@
import { BadRequestException, Body, Controller, Get, NotFoundException, Param, Post, Put, Query } from "@nestjs/common"; import { Controller, Param, Query, Body, Get, Post, BadRequestException, ParseIntPipe, Delete, Patch } from "@nestjs/common";
import { SchedulePresetsCommandService } from "../services/schedule-presets-command.service"; import { SchedulePresetsCommandService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service";
import { SchedulePresetsQueryService } from "../services/schedule-presets-query.service"; import { SchedulePresetsApplyService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto"; import { SchedulePresetsQueryService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service";
import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types"; import { SchedulePresetsUpdateDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/update-schedule-presets.dto";
import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto";
@Controller('schedule-presets') @Controller('schedule-presets')
export class SchedulePresetsController { export class SchedulePresetsController {
constructor( constructor(
private readonly commandService: SchedulePresetsCommandService, private readonly commandService: SchedulePresetsCommandService,
private readonly applyPresetsService: SchedulePresetsApplyService,
private readonly queryService: SchedulePresetsQueryService, private readonly queryService: SchedulePresetsQueryService,
){} ){}
//used to create, update or delete a schedule preset //used to create a schedule preset
@Put(':email') @Post('create/:employee_id')
async upsert( async createPreset(
@Param('email') email: string, @Param('employee_id', ParseIntPipe) employee_id: number,
@Query('action') action: UpsertAction,
@Body() dto: SchedulePresetsDto, @Body() dto: SchedulePresetsDto,
) { ) {
const actions: UpsertAction[] = ['create','update','delete']; return await this.commandService.createPreset(employee_id, dto);
if(!actions) throw new NotFoundException(`No action found for ${actions}`)
return this.commandService.upsertSchedulePreset(email, action, dto);
} }
//used to show the list of available schedule presets //used to update an already existing schedule preset
@Get(':email') @Patch('update/:preset_id')
async findListByEmail( async updatePreset(
@Param('email') email: string, @Param('preset_id', ParseIntPipe) preset_id: number,
@Body() dto: SchedulePresetsUpdateDto,
) { ) {
return this.queryService.findSchedulePresetsByEmail(email); return await this.commandService.updatePreset(preset_id, dto);
} }
//used to delete a schedule preset
@Delete('delete/:preset_id')
async deletePreset(
@Param('preset_id') preset_id: number,
) {
return await this.commandService.deletePreset(preset_id);
}
//used to show the list of available schedule presets
@Get('find/:employee_id')
async findListById(
@Param('employee_id', ParseIntPipe) employee_id: number,
) {
return this.queryService.findSchedulePresets(employee_id);
}
//used to apply a preset to a timesheet //used to apply a preset to a timesheet
@Post('/apply-presets/:email') @Post('/apply-presets/:employee_id')
async applyPresets( async applyPresets(
@Param('email') email: string, @Param('employee_id') employee_id: number,
@Query('preset') preset_name: string, @Query('preset') preset_name: string,
@Query('start') start_date: string, @Query('start') start_date: string,
) { ) {
if(!preset_name?.trim()) throw new BadRequestException('Query "preset" is required'); if(!preset_name?.trim()) throw new BadRequestException('Query "preset" is required');
if(!start_date?.trim()) throw new BadRequestException('Query "start" is required YYYY-MM-DD'); if(!start_date?.trim()) throw new BadRequestException('Query "start" is required YYYY-MM-DD');
return this.applyPresets(email, preset_name, start_date); return this.applyPresetsService.applyToTimesheet(employee_id, preset_name, start_date);
} }
} }

View File

@ -1,10 +1,14 @@
import { IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches, Min } from "class-validator"; import { IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches, Min } from "class-validator";
import { HH_MM_REGEX } from "src/time-and-attendance/utils/constants.utils";
import { Weekday } from "@prisma/client"; import { Weekday } from "@prisma/client";
export class SchedulePresetShiftsDto { export class SchedulePresetShiftsDto {
@IsEnum(Weekday) @IsEnum(Weekday)
week_day!: Weekday; week_day!: Weekday;
@IsInt()
preset_id!: number;
@IsInt() @IsInt()
@Min(1) @Min(1)
sort_order!: number; sort_order!: number;

View File

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

View File

@ -1,9 +1,10 @@
import { Module } from "@nestjs/common"; import { SchedulePresetsCommandService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service";
import { SchedulePresetsCommandService } from "./services/schedule-presets-command.service"; import { SchedulePresetsApplyService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsQueryService } from "./services/schedule-presets-query.service"; import { SchedulePresetsQueryService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service";
import { SchedulePresetsController } from "./controller/schedule-presets.controller"; import { SchedulePresetsController } from "src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller";
import { SchedulePresetsApplyService } from "./services/schedule-presets-apply.service";
import { SharedModule } from "src/time-and-attendance/modules/shared/shared.module"; import { SharedModule } from "src/time-and-attendance/modules/shared/shared.module";
import { Module } from "@nestjs/common";
@Module({ @Module({
imports: [SharedModule], imports: [SharedModule],

View File

@ -1,28 +1,23 @@
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { Weekday, Prisma } from "@prisma/client";
import { DATE_ISO_FORMAT } from "src/time-and-attendance/utils/constants.utils";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { Prisma, Weekday } from "@prisma/client";
import { WEEKDAY } from "../../../../utils/mappers.utils";
import { ApplyResult } from "src/time-and-attendance/utils/type.utils"; import { ApplyResult } from "src/time-and-attendance/utils/type.utils";
import { WEEKDAY } from "src/time-and-attendance/utils/mappers.utils";
@Injectable() @Injectable()
export class SchedulePresetsApplyService { export class SchedulePresetsApplyService {
constructor( constructor( private readonly prisma: PrismaService) {}
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
) {}
async applyToTimesheet( async applyToTimesheet(
email: string, employee_id: number,
preset_name: string, preset_name: string,
start_date_iso: string, start_date_iso: string,
): Promise<ApplyResult> { ): Promise<ApplyResult> {
if(!preset_name?.trim()) throw new BadRequestException('A preset_name is required'); if(!preset_name?.trim()) throw new BadRequestException('A preset_name is required');
if(!DATE_ISO_FORMAT.test(start_date_iso)) throw new BadRequestException('start_date must be of format :YYYY-MM-DD'); if(!DATE_ISO_FORMAT.test(start_date_iso)) throw new BadRequestException('start_date must be of format :YYYY-MM-DD');
const employee_id = await this.emailResolver.findIdByEmail(email);
if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
const preset = await this.prisma.schedulePresets.findFirst({ const preset = await this.prisma.schedulePresets.findFirst({
where: { employee_id, name: preset_name }, where: { employee_id, name: preset_name },
include: { include: {

View File

@ -1,73 +1,31 @@
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common";
import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto";
import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils"; import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types";
import { PrismaService } from "src/prisma/prisma.service";
import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto";
import { Prisma, Weekday } from "@prisma/client"; import { Prisma, Weekday } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import { toHHmmFromDate } from "src/time-and-attendance/utils/date-time.utils";
type DeleteResult = { ok: true; id: number; } | { ok: false; id: number; error: any };
type CreateResult = { ok: true; } | { ok: false; error: any };
type UpdateResult = { ok: true; id: number; data: SchedulePresetsDto } | { ok: false; id: number; error: any };
@Injectable() @Injectable()
export class SchedulePresetsCommandService { export class SchedulePresetsCommandService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver : BankCodesResolver, private readonly typeResolver : BankCodesResolver,
){} ){}
//_________________________________________________________________
// MASTER CRUD FUNCTION
//_________________________________________________________________
async upsertSchedulePreset(
email: string,
action: UpsertAction,
dto: SchedulePresetsDto,
): Promise<{
action: UpsertAction;
preset_id?: number;
total_items?: number;
}>{
if(!dto.name?.trim()) throw new BadRequestException(`A Name is required`);
//resolve employee_id using email
const employee_id = await this.emailResolver.findIdByEmail(email);
if(!employee_id) throw new NotFoundException(`employee with email: ${email} not found`);
//DELETE
if(action === 'delete') {
return this.deletePreset(employee_id, dto.name);
}
if(!Array.isArray(dto.preset_shifts) || dto.preset_shifts.length === 0) {
throw new BadRequestException(`Empty array, no detected shifts`);
}
const shifts_data = await this.resolveAndBuildPresetShifts(dto);
//CREATE AND UPDATE
if(action === 'create') {
return this.createPreset(employee_id, dto, shifts_data);
} else if (action === 'update') {
return this.updatePreset(employee_id, dto, shifts_data);
}
throw new BadRequestException(`Unknown action: ${ action }`);
}
//_________________________________________________________________ //_________________________________________________________________
// CREATE // CREATE
//_________________________________________________________________ //_________________________________________________________________
private async createPreset( async createPreset( employee_id: number, dto: SchedulePresetsDto): Promise<CreateResult> {
employee_id: number,
dto: SchedulePresetsDto,
shifts_data: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[],
): Promise<{
action: UpsertAction;
preset_id: number;
total_items: number;
}> {
try { try {
const result = await this.prisma.$transaction(async (tx)=> { const shifts_data = await this.resolveAndBuildPresetShifts(dto);
if(!shifts_data) throw new BadRequestException(`Employee with id: ${employee_id} or dto not found`);
await this.prisma.$transaction(async (tx)=> {
if(dto.is_default) { if(dto.is_default) {
await tx.schedulePresets.updateMany({ await tx.schedulePresets.updateMany({
where: { employee_id, is_default: true }, where: { is_default: true, employee_id },
data: { is_default: false }, data: { is_default: false },
}); });
} }
@ -78,56 +36,53 @@ export class SchedulePresetsCommandService {
is_default: !!dto.is_default, is_default: !!dto.is_default,
shifts: { create: shifts_data}, shifts: { create: shifts_data},
}, },
include: { shifts: true },
}); });
return created; return created;
}); });
return { action: 'create', preset_id: result.id, total_items: result.shifts.length }; return { ok: true };
} catch (error: unknown) { } catch (error: unknown) {
if(error instanceof Prisma.PrismaClientKnownRequestError){ return { ok: false, error };
if(error?.code === 'P2002') {
throw new ConflictException(`The name ${dto.name} is already used for another schedule preset`);
}
if (error.code === 'P2003' || error.code === 'P2011') {
throw new ConflictException('Invalid constraint on preset shifts');
}
}
throw error;
} }
} }
//_________________________________________________________________ //_________________________________________________________________
// UPDATE // UPDATE
//_________________________________________________________________ //_________________________________________________________________
private async updatePreset( async updatePreset( preset_id: number, dto: SchedulePresetsDto ): Promise<UpdateResult> {
employee_id: number, try {
dto: SchedulePresetsDto,
shifts_data: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[],
): Promise<{
action: UpsertAction;
preset_id?: number;
total_items?: number;
}> {
const existing = await this.prisma.schedulePresets.findFirst({ const existing = await this.prisma.schedulePresets.findFirst({
where: { employee_id, name: dto.name }, where: { id: preset_id },
select: { id:true, is_default: true }, select: {
id:true,
is_default: true,
employee_id: true,
},
}); });
if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`); if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`);
try { const shifts_data = await this.resolveAndBuildPresetShifts(dto);
const result = await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
if(typeof dto.is_default === 'boolean'){ if(typeof dto.is_default === 'boolean'){
if(dto.is_default) { if(dto.is_default) {
await tx.schedulePresets.updateMany({ await tx.schedulePresets.updateMany({
where: { employee_id, is_default: true, NOT: { id: existing.id } }, where: {
employee_id: existing.employee_id,
is_default: true,
NOT: { id: existing.id },
},
data: { is_default: false }, data: { is_default: false },
}); });
} }
await tx.schedulePresets.update({ await tx.schedulePresets.update({
where: { id: existing.id }, where: { id: existing.id },
data: { is_default: dto.is_default }, data: {
is_default: dto.is_default,
name: dto.name,
},
}); });
} }
if(shifts_data.length <= 0) throw new BadRequestException('Preset shifts to update not found');
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } }); await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
@ -148,45 +103,62 @@ export class SchedulePresetsCommandService {
}; };
}); });
await tx.schedulePresetShifts.createMany({data: create_many_data}); await tx.schedulePresetShifts.createMany({data: create_many_data});
const count = await tx.schedulePresetShifts.count({ where: { preset_id: existing.id } });
return { id: existing.id, total: count };
}); });
return { action: 'update', preset_id: result.id, total_items: result.total };
} catch (error: unknown){ const saved = await this.prisma.schedulePresets.findUnique({
if(error instanceof Prisma.PrismaClientKnownRequestError){ where: { id: existing.id },
if(error?.code === 'P2003' || error?.code === 'P2011') { include: { shifts: {
throw new ConflictException(`Invalid constraint on preset shifts`); orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }],
} include: { bank_code: { select: { type: true }}},
} }},
throw error; });
if(!saved) throw new NotFoundException(`Preset with id: ${existing.id} not found`);
const response_dto: SchedulePresetsDto = {
name: saved.name,
is_default: saved.is_default,
preset_shifts: saved.shifts.map((shift) => ({
preset_id: shift.preset_id,
week_day: shift.week_day,
sort_order: shift.sort_order,
type: shift.bank_code.type,
start_time: toHHmmFromDate(shift.start_time),
end_time: toHHmmFromDate(shift.end_time),
is_remote: shift.is_remote,
})),
};
return { ok: true, id: existing.id, data: response_dto };
} catch (error){
return { ok: false, id: preset_id, error }
} }
} }
//_________________________________________________________________ //_________________________________________________________________
// DELETE // DELETE
//_________________________________________________________________ //_________________________________________________________________
private async deletePreset( async deletePreset( preset_id: number ): Promise <DeleteResult> {
employee_id: number, try {
name: string, await this.prisma.$transaction(async (tx) => {
): Promise<{ const preset = await tx.schedulePresets.findFirst({
action: UpsertAction; where: { id: preset_id },
preset_id?: number;
total_items?: number;
}> {
const existing = await this.prisma.schedulePresets.findFirst({
where: { employee_id, name },
select: { id: true }, select: { id: true },
}); });
if(!existing) throw new NotFoundException(`Preset "${name}" not found`); if(!preset) throw new NotFoundException(`Preset with id ${ preset_id } not found`);
await this.prisma.$transaction(async (tx) => { await tx.schedulePresets.delete({where: { id: preset_id } });
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
await tx.schedulePresets.delete({where: { id: existing.id } }); return { success: true };
}); });
return { action: 'delete', preset_id: existing.id, total_items: 0 }; return { ok: true, id: preset_id };
} catch (error) {
if(error) throw new NotFoundException(`Preset schedule with id ${ preset_id } not found`);
return { ok: false, id: preset_id, error };
}
} }
//PRIVATE HELPER //PRIVATE HELPERS
//resolve bank_code_id using type and convert hours to TIME and valid shifts end/start //resolve bank_code_id using type and convert hours to TIME and valid shifts end/start
private async resolveAndBuildPresetShifts( private async resolveAndBuildPresetShifts(
dto: SchedulePresetsDto dto: SchedulePresetsDto

View File

@ -1,20 +1,13 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { PrismaService } from "src/prisma/prisma.service";
import { Prisma } from "@prisma/client";
import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils"; 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";
@Injectable() @Injectable()
export class SchedulePresetsQueryService { export class SchedulePresetsQueryService {
constructor( constructor( private readonly prisma: PrismaService ){}
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
){}
async findSchedulePresetsByEmail(email:string): Promise<PresetResponse[]> {
const employee_id = await this.emailResolver.findIdByEmail(email);
if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
async findSchedulePresets(employee_id: number): Promise<PresetResponse[]> {
try { try {
const presets = await this.prisma.schedulePresets.findMany({ const presets = await this.prisma.schedulePresets.findMany({
where: { employee_id }, where: { employee_id },

View File

@ -1,8 +1,8 @@
import { BadRequestException, Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common"; import { BadRequestException, Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common";
import { CreateResult, UpdateResult } from "src/time-and-attendance/utils/type.utils"; import { CreateResult, UpdateResult } from "src/time-and-attendance/utils/type.utils";
import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service"; import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service";
import { ShiftDto } from "../dtos/shift-create.dto";
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto"; import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
import { ShiftDto } from "../dtos/shift-create.dto";
@Controller('shift') @Controller('shift')

View File

@ -1,6 +1,6 @@
import { PartialType, OmitType } from "@nestjs/swagger"; import { PartialType, OmitType } from "@nestjs/swagger";
import { IsInt } from "class-validator";
import { ShiftDto } from "./shift-create.dto"; import { ShiftDto } from "./shift-create.dto";
import { IsInt } from "class-validator";
export class UpdateShiftDto extends PartialType( export class UpdateShiftDto extends PartialType(
// allows update using ShiftDto and preventing OmitType variables to be modified // allows update using ShiftDto and preventing OmitType variables to be modified

View File

@ -1,6 +1,6 @@
import { Injectable } from "@nestjs/common";
import { ShiftsArchive } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { ShiftsArchive } from "@prisma/client";
import { Injectable } from "@nestjs/common";
/** /**
* _____________________________________________________________________________________ * _____________________________________________________________________________________

View File

@ -1,8 +1,8 @@
import { toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils";
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { GetShiftDto } from "../dtos/shift-get.dto";
import { toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils";
import { shift_select } from "src/time-and-attendance/utils/selects.utils"; import { shift_select } from "src/time-and-attendance/utils/selects.utils";
import { GetShiftDto } from "../dtos/shift-get.dto";
/** /**
* _____________________________________________________________________________________ * _____________________________________________________________________________________

View File

@ -1,15 +1,14 @@
import { CreateResult, NormedOk, NormedErr, UpdatePayload, UpdateResult, Normalized, UpdateChanges } from "src/time-and-attendance/utils/type.utils"; import { CreateResult, NormedOk, NormedErr, UpdatePayload, UpdateResult, Normalized, UpdateChanges } from "src/time-and-attendance/utils/type.utils";
import { toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils"; import { overlaps, toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils";
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service"; import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { shift_select } from "src/time-and-attendance/utils/selects.utils"; import { shift_select } from "src/time-and-attendance/utils/selects.utils";
import { GetShiftDto } from "../dtos/shift-get.dto"; import { GetShiftDto } from "../dtos/shift-get.dto";
import { ShiftDto } from "../dtos/shift-create.dto"; import { ShiftDto } from "../dtos/shift-create.dto";
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) =>
!(a.end <= b.start || a.start >= b.end);
@Injectable() @Injectable()
export class ShiftsUpsertService { export class ShiftsUpsertService {

View File

@ -1,5 +1,6 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/time-and-attendance/utils/constants.utils";
import { sevenDaysFrom, toStringFromDate, toHHmmFromDate, toDateFromString } from "src/time-and-attendance/utils/date-time.utils"; import { sevenDaysFrom, toStringFromDate, toHHmmFromDate, toDateFromString } from "src/time-and-attendance/utils/date-time.utils";
import { TotalExpenses, TotalHours } from "src/time-and-attendance/utils/type.utils"; import { TotalExpenses, TotalHours } from "src/time-and-attendance/utils/type.utils";

View File

@ -1,12 +1,12 @@
const NUMBER_OF_TIMESHEETS_TO_RETURN = 2; export const NUMBER_OF_TIMESHEETS_TO_RETURN = 2;
const DAILY_LIMIT_HOURS = 8; export const DAILY_LIMIT_HOURS = 8;
const WEEKLY_LIMIT_HOURS = 40; export const WEEKLY_LIMIT_HOURS = 40;
const PAY_PERIOD_ANCHOR = 2023-12-17; export const PAY_PERIOD_ANCHOR = 2023-12-17;
const ANCHOR_ISO = '2023-12-17'; // ancre date export const ANCHOR_ISO = '2023-12-17'; // ancre date
const PERIOD_DAYS = 14; export const PERIOD_DAYS = 14;
const PERIODS_PER_YEAR = 26; export const PERIODS_PER_YEAR = 26;
const MS_PER_DAY = 86_400_000; export const MS_PER_DAY = 86_400_000;
//REGEX CONSTANTS //REGEX CONSTANTS
const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/; export const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/;
const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/; export const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/;

View File

@ -1,3 +1,5 @@
import { ANCHOR_ISO, MS_PER_DAY, PERIODS_PER_YEAR, PERIOD_DAYS } from "src/time-and-attendance/utils/constants.utils";
//ensures the week starts from sunday //ensures the week starts from sunday
export function weekStartSunday(date_local: Date): Date { export function weekStartSunday(date_local: Date): Date {
const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate())); const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate()));
@ -84,3 +86,6 @@ export function computePeriod(pay_year: number, period_no: number, anchorISO = A
export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) { export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) {
return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO)); return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO));
} }
export const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) =>
!(a.end <= b.start || a.start >= b.end);

View File

@ -62,3 +62,5 @@ export type ApplyResult = {
} }
export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>; export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;
export type UpsertAction = 'create' | 'update' | 'delete';