Merge branch 'main' of https://git.targo.ca/Targo/targo_backend
This commit is contained in:
commit
e8bd0403ea
|
|
@ -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": {
|
},
|
||||||
"operationId": "SchedulePresetsController_findListByEmail",
|
"/schedule-presets/delete/{preset_id}": {
|
||||||
|
"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": {}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export type UpsertAction = 'create' | 'update' | 'delete';
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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{}
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,31 @@
|
||||||
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common";
|
||||||
import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils";
|
import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto";
|
||||||
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
|
import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils";
|
||||||
import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types";
|
import { Prisma, Weekday } from "@prisma/client";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto";
|
import { toHHmmFromDate } from "src/time-and-attendance/utils/date-time.utils";
|
||||||
import { Prisma, Weekday } from "@prisma/client";
|
|
||||||
|
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,
|
|
||||||
dto: SchedulePresetsDto,
|
|
||||||
shifts_data: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[],
|
|
||||||
): Promise<{
|
|
||||||
action: UpsertAction;
|
|
||||||
preset_id?: number;
|
|
||||||
total_items?: number;
|
|
||||||
}> {
|
|
||||||
const existing = await this.prisma.schedulePresets.findFirst({
|
|
||||||
where: { employee_id, name: dto.name },
|
|
||||||
select: { id:true, is_default: true },
|
|
||||||
});
|
|
||||||
if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.prisma.$transaction(async (tx) => {
|
const existing = await this.prisma.schedulePresets.findFirst({
|
||||||
|
where: { id: preset_id },
|
||||||
|
select: {
|
||||||
|
id:true,
|
||||||
|
is_default: true,
|
||||||
|
employee_id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`);
|
||||||
|
|
||||||
|
const shifts_data = await this.resolveAndBuildPresetShifts(dto);
|
||||||
|
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;
|
select: { id: true },
|
||||||
total_items?: number;
|
});
|
||||||
}> {
|
if(!preset) throw new NotFoundException(`Preset with id ${ preset_id } not found`);
|
||||||
const existing = await this.prisma.schedulePresets.findFirst({
|
await tx.schedulePresets.delete({where: { id: preset_id } });
|
||||||
where: { employee_id, name },
|
|
||||||
select: { id: true },
|
return { success: true };
|
||||||
});
|
});
|
||||||
if(!existing) throw new NotFoundException(`Preset "${name}" not found`);
|
return { ok: true, id: preset_id };
|
||||||
await this.prisma.$transaction(async (tx) => {
|
|
||||||
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
|
} catch (error) {
|
||||||
await tx.schedulePresets.delete({where: { id: existing.id } });
|
if(error) throw new NotFoundException(`Preset schedule with id ${ preset_id } not found`);
|
||||||
});
|
return { ok: false, id: preset_id, error };
|
||||||
return { action: 'delete', preset_id: existing.id, total_items: 0 };
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//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
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _____________________________________________________________________________________
|
* _____________________________________________________________________________________
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _____________________________________________________________________________________
|
* _____________________________________________________________________________________
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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$/;
|
||||||
|
|
|
||||||
|
|
@ -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()));
|
||||||
|
|
@ -83,4 +85,7 @@ export function computePeriod(pay_year: number, period_no: number, anchorISO = A
|
||||||
//list of all 26 periods for a full year
|
//list of all 26 periods for a full year
|
||||||
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);
|
||||||
|
|
@ -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';
|
||||||
Loading…
Reference in New Issue
Block a user