diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 0462102..85ce3df 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -828,30 +828,6 @@ "Expense" ] } - }, - "/archives/expenses": { - "get": { - "operationId": "ExpensesArchiveController_findOneArchived", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "number" - } - } - ], - "responses": { - "200": { - "description": "Archived expense found" - } - }, - "summary": "Fetch expense in archives with its Id", - "tags": [ - "Expense Archives" - ] - } } }, "info": { diff --git a/src/app.module.ts b/src/app.module.ts index 4519929..c8035c4 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,17 +2,17 @@ import { BadRequestException, Module, ValidationPipe } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; // import { ArchivalModule } from './modules/archival/archival.module'; -import { AuthenticationModule } from './modules/authentication/auth.module'; +import { AuthenticationModule } from './identity-and-account/authentication/auth.module'; // import { BankCodesModule } from './modules/bank-codes/bank-codes.module'; // import { CsvExportModule } from './modules/exports/csv-exports.module'; import { HealthModule } from './health/health.module'; import { HealthController } from './health/health.controller'; import { NotificationsModule } from './modules/notifications/notifications.module'; -import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module'; -import { PreferencesModule } from './modules/preferences/preferences.module'; +import { OauthSessionsModule } from './identity-and-account/oauth-sessions/oauth-sessions.module'; +import { PreferencesModule } from './identity-and-account/preferences/preferences.module'; import { PrismaModule } from './prisma/prisma.module'; import { ScheduleModule } from '@nestjs/schedule'; -import { UsersModule } from './modules/users-management/users.module'; +import { UsersModule } from './identity-and-account/users-management/users.module'; import { ConfigModule } from '@nestjs/config'; import { APP_FILTER, APP_PIPE } from '@nestjs/core'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; diff --git a/src/modules/authentication/auth.module.ts b/src/identity-and-account/authentication/auth.module.ts similarity index 100% rename from src/modules/authentication/auth.module.ts rename to src/identity-and-account/authentication/auth.module.ts diff --git a/src/modules/authentication/controllers/auth.controller.ts b/src/identity-and-account/authentication/controllers/auth.controller.ts similarity index 96% rename from src/modules/authentication/controllers/auth.controller.ts rename to src/identity-and-account/authentication/controllers/auth.controller.ts index 248c4d1..15255f1 100644 --- a/src/modules/authentication/controllers/auth.controller.ts +++ b/src/identity-and-account/authentication/controllers/auth.controller.ts @@ -1,26 +1,26 @@ -import { Controller, Get, Req, Res, UnauthorizedException, UseGuards } from '@nestjs/common'; -import { OIDCLoginGuard } from '../guards/authentik-auth.guard'; -import { Request, Response } from 'express'; - -@Controller('auth') -export class AuthController { - - @UseGuards(OIDCLoginGuard) - @Get('/v1/login') - login() { } - - @Get('/callback') - @UseGuards(OIDCLoginGuard) - loginCallback(@Req() req: Request, @Res() res: Response) { - res.redirect('http://localhost:9000/#/login-success'); - } - - @Get('/me') - getProfile(@Req() req: Request) { - if (!req.user) { - throw new UnauthorizedException('Not logged in'); - } - return req.user; - } - -} +import { Controller, Get, Req, Res, UnauthorizedException, UseGuards } from '@nestjs/common'; +import { OIDCLoginGuard } from '../guards/authentik-auth.guard'; +import { Request, Response } from 'express'; + +@Controller('auth') +export class AuthController { + + @UseGuards(OIDCLoginGuard) + @Get('/v1/login') + login() { } + + @Get('/callback') + @UseGuards(OIDCLoginGuard) + loginCallback(@Req() req: Request, @Res() res: Response) { + res.redirect('http://localhost:9000/#/login-success'); + } + + @Get('/me') + getProfile(@Req() req: Request) { + if (!req.user) { + throw new UnauthorizedException('Not logged in'); + } + return req.user; + } + +} diff --git a/src/modules/authentication/guards/authentik-auth.guard.ts b/src/identity-and-account/authentication/guards/authentik-auth.guard.ts similarity index 97% rename from src/modules/authentication/guards/authentik-auth.guard.ts rename to src/identity-and-account/authentication/guards/authentik-auth.guard.ts index df9208c..ff4f44d 100644 --- a/src/modules/authentication/guards/authentik-auth.guard.ts +++ b/src/identity-and-account/authentication/guards/authentik-auth.guard.ts @@ -1,12 +1,12 @@ -import { ExecutionContext, Injectable } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; - -@Injectable() -export class OIDCLoginGuard extends AuthGuard('openidconnect') { - async canActivate(context: ExecutionContext) { - const result = (await super.canActivate(context)) as boolean; - const request = context.switchToHttp().getRequest(); - await super.logIn(request); - return result; - } +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class OIDCLoginGuard extends AuthGuard('openidconnect') { + async canActivate(context: ExecutionContext) { + const result = (await super.canActivate(context)) as boolean; + const request = context.switchToHttp().getRequest(); + await super.logIn(request); + return result; + } } \ No newline at end of file diff --git a/src/modules/authentication/serializers/express-session.serializer.ts b/src/identity-and-account/authentication/serializers/express-session.serializer.ts similarity index 100% rename from src/modules/authentication/serializers/express-session.serializer.ts rename to src/identity-and-account/authentication/serializers/express-session.serializer.ts diff --git a/src/modules/authentication/services/authentik-auth.service.ts b/src/identity-and-account/authentication/services/authentik-auth.service.ts similarity index 75% rename from src/modules/authentication/services/authentik-auth.service.ts rename to src/identity-and-account/authentication/services/authentik-auth.service.ts index 9d6514c..974a1eb 100644 --- a/src/modules/authentication/services/authentik-auth.service.ts +++ b/src/identity-and-account/authentication/services/authentik-auth.service.ts @@ -1,14 +1,14 @@ - -import { Injectable } from '@nestjs/common'; -import { UsersService } from 'src/modules/users-management/services/users.service'; - -@Injectable() -export class AuthentikAuthService { - constructor(private usersService: UsersService) {} - - async validateUser(user_email: string): Promise { - const user = await this.usersService.findOneByEmail(user_email); - - return user; - } -} + +import { Injectable } from '@nestjs/common'; +import { UsersService } from 'src/identity-and-account/users-management/services/users.service'; + +@Injectable() +export class AuthentikAuthService { + constructor(private usersService: UsersService) {} + + async validateUser(user_email: string): Promise { + const user = await this.usersService.findOneByEmail(user_email); + + return user; + } +} diff --git a/src/modules/authentication/strategies/authentik.strategy.ts b/src/identity-and-account/authentication/strategies/authentik.strategy.ts similarity index 97% rename from src/modules/authentication/strategies/authentik.strategy.ts rename to src/identity-and-account/authentication/strategies/authentik.strategy.ts index 98602c5..6609356 100644 --- a/src/modules/authentication/strategies/authentik.strategy.ts +++ b/src/identity-and-account/authentication/strategies/authentik.strategy.ts @@ -1,63 +1,63 @@ - -import { Strategy as OIDCStrategy, Profile, VerifyCallback } from 'passport-openidconnect'; -import { PassportStrategy } from '@nestjs/passport'; -import { Injectable } from '@nestjs/common'; -import { AuthentikAuthService } from '../services/authentik-auth.service'; -import { ValidationError } from 'class-validator'; - -export interface AuthentikPayload { - iss: string; // Issuer - sub: string; // Subject (user ID) - aud: string; // Audience (client ID) - exp: number; // Expiration time (Unix) - iat: number; // Issued at time (Unix) - auth_time: number; // Time of authentication (Unix) - acr?: string; // Auth Context Class Reference - amr?: string[]; // Auth Method References (e.g., ['pwd']) - email: string; - email_verified: boolean; - name?: string; - given_name?: string; - preferred_username?: string; - nickname?: string; - groups?: string[]; -} - -@Injectable() -export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidconnect', 8) { - constructor(private authentikAuthService: AuthentikAuthService) { - super({ - issuer: process.env.AUTHENTIK_ISSUER || "ISSUER_MISSING", - clientID: process.env.AUTHENTIK_CLIENT_ID || 'CLIENT_ID_MISSING', - clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || 'CLIENT_SECRET_MISSING', - callbackURL: process.env.AUTHENTIK_CALLBACK_URL || 'CALLBACK_URL_MISSING', - authorizationURL: process.env.AUTHENTIK_AUTH_URL || 'AUTH_URL_MISSING', - tokenURL: process.env.AUTHENTIK_TOKEN_URL || 'TOKEN_URL_MISSING', - userInfoURL: process.env.AUTHENTIK_USERINFO_URL || 'USERINFO_URL_MISSING', - scope: ['openid', 'email', 'profile', 'offline_access'], - },); - } - - async validate( - _issuer: string, - profile: Profile, - _context: any, - _idToken: string, - _accessToken: string, - _refreshToken: string, - _params: any, - cb: VerifyCallback, - ): Promise { - try { - const email = profile.emails?.[0]?.value; - if (!email) return cb(new Error('Missing email in OIDC profile'), false); - - const user = await this.authentikAuthService.validateUser(email); - if (!user) return cb(new Error('User not found'), false); - - return cb(null, user); - } catch (err) { - return cb(err, false); - } - } -} + +import { Strategy as OIDCStrategy, Profile, VerifyCallback } from 'passport-openidconnect'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; +import { AuthentikAuthService } from '../services/authentik-auth.service'; +import { ValidationError } from 'class-validator'; + +export interface AuthentikPayload { + iss: string; // Issuer + sub: string; // Subject (user ID) + aud: string; // Audience (client ID) + exp: number; // Expiration time (Unix) + iat: number; // Issued at time (Unix) + auth_time: number; // Time of authentication (Unix) + acr?: string; // Auth Context Class Reference + amr?: string[]; // Auth Method References (e.g., ['pwd']) + email: string; + email_verified: boolean; + name?: string; + given_name?: string; + preferred_username?: string; + nickname?: string; + groups?: string[]; +} + +@Injectable() +export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidconnect', 8) { + constructor(private authentikAuthService: AuthentikAuthService) { + super({ + issuer: process.env.AUTHENTIK_ISSUER || "ISSUER_MISSING", + clientID: process.env.AUTHENTIK_CLIENT_ID || 'CLIENT_ID_MISSING', + clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || 'CLIENT_SECRET_MISSING', + callbackURL: process.env.AUTHENTIK_CALLBACK_URL || 'CALLBACK_URL_MISSING', + authorizationURL: process.env.AUTHENTIK_AUTH_URL || 'AUTH_URL_MISSING', + tokenURL: process.env.AUTHENTIK_TOKEN_URL || 'TOKEN_URL_MISSING', + userInfoURL: process.env.AUTHENTIK_USERINFO_URL || 'USERINFO_URL_MISSING', + scope: ['openid', 'email', 'profile', 'offline_access'], + },); + } + + async validate( + _issuer: string, + profile: Profile, + _context: any, + _idToken: string, + _accessToken: string, + _refreshToken: string, + _params: any, + cb: VerifyCallback, + ): Promise { + try { + const email = profile.emails?.[0]?.value; + if (!email) return cb(new Error('Missing email in OIDC profile'), false); + + const user = await this.authentikAuthService.validateUser(email); + if (!user) return cb(new Error('User not found'), false); + + return cb(null, user); + } catch (err) { + return cb(err, false); + } + } +} diff --git a/src/modules/employees/controllers/employees.controller.ts b/src/identity-and-account/employees/controllers/employees.controller.ts similarity index 100% rename from src/modules/employees/controllers/employees.controller.ts rename to src/identity-and-account/employees/controllers/employees.controller.ts diff --git a/src/modules/employees/dtos/create-employee.dto.ts b/src/identity-and-account/employees/dtos/create-employee.dto.ts similarity index 100% rename from src/modules/employees/dtos/create-employee.dto.ts rename to src/identity-and-account/employees/dtos/create-employee.dto.ts diff --git a/src/modules/employees/dtos/list-employee.dto.ts b/src/identity-and-account/employees/dtos/list-employee.dto.ts similarity index 100% rename from src/modules/employees/dtos/list-employee.dto.ts rename to src/identity-and-account/employees/dtos/list-employee.dto.ts diff --git a/src/modules/employees/dtos/profil-employee.dto.ts b/src/identity-and-account/employees/dtos/profil-employee.dto.ts similarity index 100% rename from src/modules/employees/dtos/profil-employee.dto.ts rename to src/identity-and-account/employees/dtos/profil-employee.dto.ts diff --git a/src/modules/employees/dtos/update-employee.dto.ts b/src/identity-and-account/employees/dtos/update-employee.dto.ts similarity index 100% rename from src/modules/employees/dtos/update-employee.dto.ts rename to src/identity-and-account/employees/dtos/update-employee.dto.ts diff --git a/src/identity-and-account/employees/employees.module.ts b/src/identity-and-account/employees/employees.module.ts index 7ce6f04..676f4b0 100644 --- a/src/identity-and-account/employees/employees.module.ts +++ b/src/identity-and-account/employees/employees.module.ts @@ -1,13 +1,12 @@ // import { Module } from '@nestjs/common'; // import { EmployeesController } from './controllers/employees.controller'; // import { EmployeesService } from './services/employees.service'; -// import { EmployeesArchivalService } from './services/employees-archival.service'; // import { SharedModule } from '../../time-and-attendance/modules/shared/shared.module'; // @Module({ // imports: [SharedModule], // controllers: [EmployeesController], -// providers: [EmployeesService, EmployeesArchivalService], -// exports: [EmployeesService, EmployeesArchivalService], +// providers: [EmployeesService], +// exports: [EmployeesService ], // }) // export class EmployeesModule {} diff --git a/src/modules/employees/services/employees-archival.service.ts b/src/identity-and-account/employees/services/employees-archival.service.ts similarity index 100% rename from src/modules/employees/services/employees-archival.service.ts rename to src/identity-and-account/employees/services/employees-archival.service.ts diff --git a/src/modules/employees/services/employees.service.ts b/src/identity-and-account/employees/services/employees.service.ts similarity index 100% rename from src/modules/employees/services/employees.service.ts rename to src/identity-and-account/employees/services/employees.service.ts diff --git a/src/modules/employees/utils/employee.utils.ts b/src/identity-and-account/employees/utils/employee.utils.ts similarity index 100% rename from src/modules/employees/utils/employee.utils.ts rename to src/identity-and-account/employees/utils/employee.utils.ts diff --git a/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts b/src/identity-and-account/oauth-sessions/controllers/oauth-sessions.controller.ts similarity index 100% rename from src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts rename to src/identity-and-account/oauth-sessions/controllers/oauth-sessions.controller.ts diff --git a/src/modules/oauth-sessions/dtos/create-oauth-session.dto.ts b/src/identity-and-account/oauth-sessions/dtos/create-oauth-session.dto.ts similarity index 100% rename from src/modules/oauth-sessions/dtos/create-oauth-session.dto.ts rename to src/identity-and-account/oauth-sessions/dtos/create-oauth-session.dto.ts diff --git a/src/modules/oauth-sessions/dtos/update-oauth-session.dto.ts b/src/identity-and-account/oauth-sessions/dtos/update-oauth-session.dto.ts similarity index 100% rename from src/modules/oauth-sessions/dtos/update-oauth-session.dto.ts rename to src/identity-and-account/oauth-sessions/dtos/update-oauth-session.dto.ts diff --git a/src/modules/oauth-sessions/oauth-sessions.module.ts b/src/identity-and-account/oauth-sessions/oauth-sessions.module.ts similarity index 100% rename from src/modules/oauth-sessions/oauth-sessions.module.ts rename to src/identity-and-account/oauth-sessions/oauth-sessions.module.ts diff --git a/src/modules/oauth-sessions/services/oauth-sessions.service.ts b/src/identity-and-account/oauth-sessions/services/oauth-sessions.service.ts similarity index 100% rename from src/modules/oauth-sessions/services/oauth-sessions.service.ts rename to src/identity-and-account/oauth-sessions/services/oauth-sessions.service.ts diff --git a/src/modules/preferences/controllers/preferences.controller.ts b/src/identity-and-account/preferences/controllers/preferences.controller.ts similarity index 100% rename from src/modules/preferences/controllers/preferences.controller.ts rename to src/identity-and-account/preferences/controllers/preferences.controller.ts diff --git a/src/modules/preferences/dtos/preferences.dto.ts b/src/identity-and-account/preferences/dtos/preferences.dto.ts similarity index 100% rename from src/modules/preferences/dtos/preferences.dto.ts rename to src/identity-and-account/preferences/dtos/preferences.dto.ts diff --git a/src/modules/preferences/preferences.module.ts b/src/identity-and-account/preferences/preferences.module.ts similarity index 100% rename from src/modules/preferences/preferences.module.ts rename to src/identity-and-account/preferences/preferences.module.ts diff --git a/src/modules/preferences/services/preferences.service.ts b/src/identity-and-account/preferences/services/preferences.service.ts similarity index 100% rename from src/modules/preferences/services/preferences.service.ts rename to src/identity-and-account/preferences/services/preferences.service.ts diff --git a/src/modules/users-management/dtos/user.dto.ts b/src/identity-and-account/users-management/dtos/user.dto.ts similarity index 100% rename from src/modules/users-management/dtos/user.dto.ts rename to src/identity-and-account/users-management/dtos/user.dto.ts diff --git a/src/modules/users-management/services/abstract-user.service.ts b/src/identity-and-account/users-management/services/abstract-user.service.ts similarity index 100% rename from src/modules/users-management/services/abstract-user.service.ts rename to src/identity-and-account/users-management/services/abstract-user.service.ts diff --git a/src/modules/users-management/services/users.service.ts b/src/identity-and-account/users-management/services/users.service.ts similarity index 100% rename from src/modules/users-management/services/users.service.ts rename to src/identity-and-account/users-management/services/users.service.ts diff --git a/src/modules/users-management/users.module.ts b/src/identity-and-account/users-management/users.module.ts similarity index 100% rename from src/modules/users-management/users.module.ts rename to src/identity-and-account/users-management/users.module.ts diff --git a/src/modules/customers/controllers/customers.controller.ts b/src/modules/customers/controllers/customers.controller.ts deleted file mode 100644 index c066342..0000000 --- a/src/modules/customers/controllers/customers.controller.ts +++ /dev/null @@ -1,68 +0,0 @@ -// import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from '@nestjs/common'; -// import { CustomersService } from '../services/customers.service'; -// import { Customers } from '@prisma/client'; -// import { CreateCustomerDto } from '../dtos/create-customer.dto'; -// import { UpdateCustomerDto } from '../dtos/update-customer.dto'; -// import { RolesAllowed } from "src/common/decorators/roles.decorators"; -// import { Roles as RoleEnum } from '.prisma/client'; -// import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; - -// @ApiTags('Customers') -// @ApiBearerAuth('access-token') -// // @UseGuards() -// @Controller('customers') -// export class CustomersController { -// constructor(private readonly customersService: CustomersService) {} - -// //_____________________________________________________________________________________________ -// // Deprecated or unused methods -// //_____________________________________________________________________________________________ - -// // @Post() -// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.SUPERVISOR) -// // @ApiOperation({ summary: 'Create customer' }) -// // @ApiResponse({ status: 201, description: 'Customer created', type: CreateCustomerDto }) -// // @ApiResponse({ status: 400, description: 'Invalid task or invalid data' }) -// // create(@Body() dto: CreateCustomerDto): Promise { -// // return this.customersService.create(dto); -// // } - -// // @Get() -// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) -// // @ApiOperation({ summary: 'Find all customers' }) -// // @ApiResponse({ status: 201, description: 'List of customers found', type: CreateCustomerDto, isArray: true }) -// // @ApiResponse({ status: 400, description: 'List of customers not found' }) -// // findAll(): Promise { -// // return this.customersService.findAll(); -// // } - -// // @Get(':id') -// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) -// // @ApiOperation({ summary: 'Find customer' }) -// // @ApiResponse({ status: 201, description: 'Customer found', type: CreateCustomerDto }) -// // @ApiResponse({ status: 400, description: 'Customer not found' }) -// // findOne(@Param('id', ParseIntPipe) id: number): Promise { -// // return this.customersService.findOne(id); -// // } - -// // @Patch(':id') -// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE,RoleEnum.SUPERVISOR) -// // @ApiOperation({ summary: 'Update customer' }) -// // @ApiResponse({ status: 201, description: 'Customer updated', type: CreateCustomerDto }) -// // @ApiResponse({ status: 400, description: 'Customer not found' }) -// // update( -// // @Param('id', ParseIntPipe) id: number, -// // @Body() dto: UpdateCustomerDto, -// // ): Promise { -// // return this.customersService.update(id, dto); -// // } - -// // @Delete(':id') -// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.SUPERVISOR) -// // @ApiOperation({ summary: 'Delete customer' }) -// // @ApiResponse({ status: 201, description: 'Customer deleted', type: CreateCustomerDto }) -// // @ApiResponse({ status: 400, description: 'Customer not found' }) -// // remove(@Param('id', ParseIntPipe) id: number): Promise{ -// // return this.customersService.remove(id); -// // } -// } diff --git a/src/modules/customers/customers.module.ts b/src/modules/customers/customers.module.ts deleted file mode 100644 index cccb363..0000000 --- a/src/modules/customers/customers.module.ts +++ /dev/null @@ -1,10 +0,0 @@ - -// import { Module } from '@nestjs/common'; -// import { CustomersController } from './controllers/customers.controller'; -// import { CustomersService } from './services/customers.service'; - -// @Module({ -// controllers:[CustomersController], -// providers:[CustomersService], -// }) -// export class CustomersModule {} \ No newline at end of file diff --git a/src/modules/customers/dtos/create-customer.dto.ts b/src/modules/customers/dtos/create-customer.dto.ts deleted file mode 100644 index 8a136ac..0000000 --- a/src/modules/customers/dtos/create-customer.dto.ts +++ /dev/null @@ -1,79 +0,0 @@ -// import { ApiProperty } from "@nestjs/swagger"; -// import { Type } from "class-transformer"; -// import { -// Allow, -// IsEmail, -// IsInt, -// IsNotEmpty, -// IsOptional, -// IsPositive, -// IsString, -// IsUUID, -// } from "class-validator"; - -// export class CreateCustomerDto { -// @ApiProperty({ -// example: 1, -// description: 'Unique ID of a customer(primary-key, auto-incremented)', -// }) -// @Allow() -// id?: number; - -// @ApiProperty({ -// example: '0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d', -// description: 'UUID of the user linked to that customer', -// }) -// @IsUUID() -// @IsOptional() -// user_id?: string; - -// @ApiProperty({ -// example: 'Gandalf', -// description: 'Customer`s first name', -// }) -// @IsString() -// @IsNotEmpty() -// first_name: string; - -// @ApiProperty({ -// example: 'TheGray', -// description: 'Customer`s last name', -// }) -// @IsString() -// @IsNotEmpty() -// last_name: string; - -// @ApiProperty({ -// example: 'you_shall_not_pass@middleEarth.com', -// description: 'Customer`s email', -// }) -// @IsEmail() -// @IsOptional() -// email: string; - -// @ApiProperty({ -// example: '8436637464', -// description: 'Customer`s phone number', -// }) -// @IsString() -// phone_number: string; - -// @ApiProperty({ -// example: '1 Ringbearer`s way, Mount Doom city, ME, T1R 1N6 ', -// description: 'Customer`s residence', -// required: false, -// }) -// @IsString() -// @IsOptional() -// residence?: string; - -// @ApiProperty({ -// example: '4263253', -// description: 'Customer`s invoice number', -// required: false, -// }) -// @Type(() => Number) -// @IsInt() -// @IsNotEmpty() -// invoice_id: number; -// } \ No newline at end of file diff --git a/src/modules/customers/dtos/update-customer.dto.ts b/src/modules/customers/dtos/update-customer.dto.ts deleted file mode 100644 index 2f52413..0000000 --- a/src/modules/customers/dtos/update-customer.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -// import { PartialType } from "@nestjs/swagger"; -// import { CreateCustomerDto } from "./create-customer.dto"; - -// export class UpdateCustomerDto extends PartialType(CreateCustomerDto) {} \ No newline at end of file diff --git a/src/modules/customers/services/customers.service.ts b/src/modules/customers/services/customers.service.ts deleted file mode 100644 index 2662250..0000000 --- a/src/modules/customers/services/customers.service.ts +++ /dev/null @@ -1,93 +0,0 @@ -// import { Injectable } from '@nestjs/common'; - -// @Injectable() -// export class CustomersService { - -// //_____________________________________________________________________________________________ -// // Deprecated or unused methods -// //_____________________________________________________________________________________________ - -// // constructor(private readonly prisma: PrismaService) {} - -// // async create(dto: CreateCustomerDto): Promise { -// // const { -// // first_name, -// // last_name, -// // email, -// // phone_number, -// // residence, -// // invoice_id, -// // } = dto; - -// // return this.prisma.$transaction(async (transaction) => { -// // const user: Users = await transaction.users.create({ -// // data: { -// // first_name, -// // last_name, -// // email, -// // phone_number, -// // residence, -// // }, -// // }); -// // return transaction.customers.create({ -// // data: { -// // user_id: user.id, -// // invoice_id, -// // }, -// // }); -// // }); -// // } - -// // findAll(): Promise { -// // return this.prisma.customers.findMany({ -// // include: { user: true }, -// // }) -// // } - -// // async findOne(id:number): Promise { -// // const customer = await this.prisma.customers.findUnique({ -// // where: { id }, -// // include: { user: true }, -// // }); -// // if(!customer) throw new NotFoundException(`Customer #${id} not found`); -// // return customer; -// // } - -// // async update(id: number,dto: UpdateCustomerDto): Promise { -// // const customer = await this.findOne(id); - -// // const { -// // first_name, -// // last_name, -// // email, -// // phone_number, -// // residence, -// // invoice_id, -// // } = dto; - -// // return this.prisma.$transaction(async (transaction) => { -// // await transaction.users.update({ -// // where: { id: customer.user_id }, -// // data: { -// // ...(first_name !== undefined && { first_name }), -// // ...(last_name !== undefined && { last_name }), -// // ...(email !== undefined && { email }), -// // ...(phone_number !== undefined && { phone_number }), -// // ...(residence !== undefined && { residence }), -// // }, -// // }); - -// // return transaction.customers.update({ -// // where: { id }, -// // data: { -// // ...(invoice_id !== undefined && { invoice_id }), -// // }, -// // }); -// // }); -// // } - -// // async remove(id: number): Promise { -// // await this.findOne(id); -// // return this.prisma.customers.delete({ where: { id }}); -// // } -// } diff --git a/src/modules/employees/employees.module.ts b/src/modules/employees/employees.module.ts deleted file mode 100644 index 676f4b0..0000000 --- a/src/modules/employees/employees.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -// import { Module } from '@nestjs/common'; -// import { EmployeesController } from './controllers/employees.controller'; -// import { EmployeesService } from './services/employees.service'; -// import { SharedModule } from '../../time-and-attendance/modules/shared/shared.module'; - -// @Module({ -// imports: [SharedModule], -// controllers: [EmployeesController], -// providers: [EmployeesService], -// exports: [EmployeesService ], -// }) -// export class EmployeesModule {} diff --git a/src/time-and-attendance/modules/leave-requests/controllers/leave-requests.controller.ts b/src/time-and-attendance/modules/leave-requests/controllers/leave-requests.controller.ts index 7ecce7e..fc934ff 100644 --- a/src/time-and-attendance/modules/leave-requests/controllers/leave-requests.controller.ts +++ b/src/time-and-attendance/modules/leave-requests/controllers/leave-requests.controller.ts @@ -1,30 +1,30 @@ -// import { Body, Controller, Post } from "@nestjs/common"; -// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger"; -// import { LeaveRequestsService } from "../services/leave-request.service"; -// import { UpsertLeaveRequestDto } from "../dtos/upsert-leave-request.dto"; -// import { LeaveTypes } from "@prisma/client"; +import { Body, Controller, Post } from "@nestjs/common"; +import { ApiBearerAuth, ApiTags } from "@nestjs/swagger"; +import { LeaveRequestsService } from "../services/leave-request.service"; +import { UpsertLeaveRequestDto } from "../dtos/upsert-leave-request.dto"; +import { LeaveTypes } from "@prisma/client"; -// @ApiTags('Leave Requests') -// @ApiBearerAuth('access-token') -// // @UseGuards() -// @Controller('leave-requests') -// export class LeaveRequestController { -// constructor(private readonly leave_service: LeaveRequestsService){} +@ApiTags('Leave Requests') +@ApiBearerAuth('access-token') +// @UseGuards() +@Controller('leave-requests') +export class LeaveRequestController { + constructor(private readonly leave_service: LeaveRequestsService){} -// @Post('upsert') -// async upsertLeaveRequest(@Body() dto: UpsertLeaveRequestDto) { -// const { action, leave_requests } = await this.leave_service.handle(dto); -// return { action, leave_requests }; -// }q + @Post('upsert') + async upsertLeaveRequest(@Body() dto: UpsertLeaveRequestDto) { + const { action, leave_requests } = await this.leave_service.handle(dto); + return { action, leave_requests }; + }q -// //TODO: -// /* -// @Get('archive') -// findAllArchived(){...} + //TODO: + /* + @Get('archive') + findAllArchived(){...} -// @Get('archive/:id') -// findOneArchived(id){...} -// */ + @Get('archive/:id') + findOneArchived(id){...} + */ -// } +} diff --git a/src/time-and-attendance/modules/leave-requests/leave-requests.module.ts b/src/time-and-attendance/modules/leave-requests/leave-requests.module.ts index 03ad546..954f07a 100644 --- a/src/time-and-attendance/modules/leave-requests/leave-requests.module.ts +++ b/src/time-and-attendance/modules/leave-requests/leave-requests.module.ts @@ -1,29 +1,30 @@ -// import { PrismaService } from "src/prisma/prisma.service"; -// import { LeaveRequestController } from "./controllers/leave-requests.controller"; -// import { HolidayLeaveRequestsService } from "./services/holiday-leave-requests.service"; -// import { Module } from "@nestjs/common"; -// import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module"; -// import { VacationLeaveRequestsService } from "./services/vacation-leave-requests.service"; -// import { SickLeaveRequestsService } from "./services/sick-leave-requests.service"; -// import { LeaveRequestsService } from "./services/leave-request.service"; -// import { ShiftsModule } from "../shifts/shifts.module"; -// import { LeaveRequestsUtils } from "./utils/leave-request.util"; -// import { SharedModule } from "../shared/shared.module"; +import { Module } from "@nestjs/common"; +import { PrismaService } from "src/prisma/prisma.service"; +import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module"; +import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service"; +import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service"; +import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; +import { LeaveRequestController } from "src/time-and-attendance/modules/leave-requests/controllers/leave-requests.controller"; +import { LeaveRequestsService } from "src/time-and-attendance/modules/leave-requests/services/leave-request.service"; +import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util"; +import { SharedModule } from "src/time-and-attendance/modules/shared/shared.module"; +import { ShiftsModule } from "src/time-and-attendance/modules/time-tracker/shifts/shifts.module"; -// @Module({ -// imports: [BusinessLogicsModule, ShiftsModule, SharedModule], -// controllers: [LeaveRequestController], -// providers: [ -// VacationLeaveRequestsService, -// SickLeaveRequestsService, -// HolidayLeaveRequestsService, -// LeaveRequestsService, -// PrismaService, -// LeaveRequestsUtils, -// ], -// exports: [ -// LeaveRequestsService, -// ], -// }) -// export class LeaveRequestsModule {} \ No newline at end of file +@Module({ + imports: [BusinessLogicsModule, ShiftsModule, SharedModule], + controllers: [LeaveRequestController], + providers: [ + VacationService, + SickLeaveService, + HolidayService, + LeaveRequestsService, + PrismaService, + LeaveRequestsUtils, + ], + exports: [ + LeaveRequestsService, + ], +}) + +export class LeaveRequestsModule {} \ No newline at end of file diff --git a/src/time-and-attendance/modules/leave-requests/services/holiday-leave-requests.service.ts b/src/time-and-attendance/modules/leave-requests/services/holiday-leave-requests.service.ts index ecfa8cc..43f4716 100644 --- a/src/time-and-attendance/modules/leave-requests/services/holiday-leave-requests.service.ts +++ b/src/time-and-attendance/modules/leave-requests/services/holiday-leave-requests.service.ts @@ -1,78 +1,78 @@ -// import { UpsertLeaveRequestDto, UpsertResult } from '../dtos/upsert-leave-request.dto'; -// import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto'; -// import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; -// import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client'; -// import { HolidayService } from 'src/modules/business-logics/services/holiday.service'; -// import { PrismaService } from 'src/prisma/prisma.service'; -// import { mapRowToView } from '../mappers/leave-requests.mapper'; -// import { leaveRequestsSelect } from '../utils/leave-requests.select'; -// import { LeaveRequestsUtils} from '../utils/leave-request.util'; -// import { normalizeDates, toDateOnly } from 'src/modules/shared/helpers/date-time.helpers'; -// import { BankCodesResolver } from 'src/modules/shared/utils/resolve-bank-type-id.utils'; -// import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils'; +import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; +import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client"; +import { PrismaService } from "src/prisma/prisma.service"; +import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service"; +import { LeaveRequestViewDto } from "src/time-and-attendance/modules/leave-requests/dtos/leave-request-view.dto"; +import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/modules/leave-requests/dtos/upsert-leave-request.dto"; +import { mapRowToView } from "src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper"; +import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util"; +import { leaveRequestsSelect } from "src/time-and-attendance/modules/leave-requests/utils/leave-requests.select"; +import { normalizeDates, toDateOnly } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers"; +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"; -// @Injectable() -// export class HolidayLeaveRequestsService { -// constructor( -// private readonly prisma: PrismaService, -// private readonly holidayService: HolidayService, -// private readonly leaveUtils: LeaveRequestsUtils, -// private readonly emailResolver: EmailToIdResolver, -// private readonly typeResolver: BankCodesResolver, -// ) {} +@Injectable() +export class HolidayLeaveRequestsService { + constructor( + private readonly prisma: PrismaService, + private readonly holidayService: HolidayService, + private readonly leaveUtils: LeaveRequestsUtils, + private readonly emailResolver: EmailToIdResolver, + private readonly typeResolver: BankCodesResolver, + ) {} -// async create(dto: UpsertLeaveRequestDto): Promise { -// const email = dto.email.trim(); -// const employee_id = await this.emailResolver.findIdByEmail(email); -// const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY); -// if(!bank_code) throw new NotFoundException(`bank_code not found`); -// const dates = normalizeDates(dto.dates); -// if (!dates.length) throw new BadRequestException('Dates array must not be empty'); + async create(dto: UpsertLeaveRequestDto): Promise { + const email = dto.email.trim(); + const employee_id = await this.emailResolver.findIdByEmail(email); + const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY); + if(!bank_code) throw new NotFoundException(`bank_code not found`); + const dates = normalizeDates(dto.dates); + if (!dates.length) throw new BadRequestException('Dates array must not be empty'); -// const created: LeaveRequestViewDto[] = []; + const created: LeaveRequestViewDto[] = []; -// for (const iso_date of dates) { -// const date = toDateOnly(iso_date); + for (const iso_date of dates) { + const date = toDateOnly(iso_date); -// const existing = await this.prisma.leaveRequests.findUnique({ -// where: { -// leave_per_employee_date: { -// employee_id: employee_id, -// leave_type: LeaveTypes.HOLIDAY, -// date, -// }, -// }, -// select: { id: true }, -// }); -// if (existing) { -// throw new BadRequestException(`Holiday request already exists for ${iso_date}`); -// } + const existing = await this.prisma.leaveRequests.findUnique({ + where: { + leave_per_employee_date: { + employee_id: employee_id, + leave_type: LeaveTypes.HOLIDAY, + date, + }, + }, + select: { id: true }, + }); + if (existing) { + throw new BadRequestException(`Holiday request already exists for ${iso_date}`); + } -// const payable = await this.holidayService.calculateHolidayPay(email, date, bank_code.modifier); -// const row = await this.prisma.leaveRequests.create({ -// data: { -// employee_id: employee_id, -// bank_code_id: bank_code.id, -// leave_type: LeaveTypes.HOLIDAY, -// date, -// comment: dto.comment ?? '', -// requested_hours: dto.requested_hours ?? 8, -// payable_hours: payable, -// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING, -// }, -// select: leaveRequestsSelect, -// }); + const payable = await this.holidayService.calculateHolidayPay(email, date, bank_code.modifier); + const row = await this.prisma.leaveRequests.create({ + data: { + employee_id: employee_id, + bank_code_id: bank_code.id, + leave_type: LeaveTypes.HOLIDAY, + date, + comment: dto.comment ?? '', + requested_hours: dto.requested_hours ?? 8, + payable_hours: payable, + approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING, + }, + select: leaveRequestsSelect, + }); -// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); -// if (row.approval_status === LeaveApprovalStatus.APPROVED) { -// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours,LeaveTypes.HOLIDAY, row.comment); -// } + const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); + if (row.approval_status === LeaveApprovalStatus.APPROVED) { + await this.leaveUtils.syncShift(email, employee_id, iso_date, hours,LeaveTypes.HOLIDAY, row.comment); + } -// created.push({ ...mapRowToView(row), action: 'create' }); -// } + created.push({ ...mapRowToView(row), action: 'create' }); + } -// return { action: 'create', leave_requests: created }; -// } -// } + return { action: 'create', leave_requests: created }; + } +} diff --git a/src/time-and-attendance/modules/leave-requests/services/leave-request.service.ts b/src/time-and-attendance/modules/leave-requests/services/leave-request.service.ts index 7b0c82e..8b44ac0 100644 --- a/src/time-and-attendance/modules/leave-requests/services/leave-request.service.ts +++ b/src/time-and-attendance/modules/leave-requests/services/leave-request.service.ts @@ -1,248 +1,243 @@ -// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; -// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client"; -// import { roundToQuarterHour } from "src/common/utils/date-utils"; -// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto"; -// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; -// import { mapRowToView } from "../mappers/leave-requests.mapper"; -// import { leaveRequestsSelect } from "../utils/leave-requests.select"; -// import { HolidayLeaveRequestsService } from "./holiday-leave-requests.service"; -// import { SickLeaveRequestsService } from "./sick-leave-requests.service"; -// import { VacationLeaveRequestsService } from "./vacation-leave-requests.service"; -// import { HolidayService } from "src/modules/business-logics/services/holiday.service"; -// import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service"; -// import { VacationService } from "src/modules/business-logics/services/vacation.service"; -// import { PrismaService } from "src/prisma/prisma.service"; -// import { LeaveRequestsUtils } from "../utils/leave-request.util"; -// import { normalizeDates, toDateOnly, toISODateKey } from "src/modules/shared/helpers/date-time.helpers"; -// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils"; -// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils"; +import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; +import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client"; +import { roundToQuarterHour } from "src/common/utils/date-utils"; +import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto"; +import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; +import { mapRowToView } from "../mappers/leave-requests.mapper"; +import { leaveRequestsSelect } from "../utils/leave-requests.select"; +import { PrismaService } from "src/prisma/prisma.service"; +import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service"; +import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service"; +import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; +import { normalizeDates, toDateOnly, toISODateKey } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers"; +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 { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util"; +@Injectable() +export class LeaveRequestsService { + constructor( + private readonly prisma: PrismaService, + private readonly holidayService: HolidayService, + private readonly sickLogic: SickLeaveService, + private readonly sickLeaveService: SickLeaveService, + private readonly vacationService: VacationService, + private readonly vacationLogic: VacationService, + private readonly leaveUtils: LeaveRequestsUtils, + private readonly emailResolver: EmailToIdResolver, + private readonly typeResolver: BankCodesResolver, + ) {} -// @Injectable() -// export class LeaveRequestsService { -// constructor( -// private readonly prisma: PrismaService, -// private readonly holidayLeaveService: HolidayLeaveRequestsService, -// private readonly holidayService: HolidayService, -// private readonly sickLogic: SickLeaveService, -// private readonly sickLeaveService: SickLeaveRequestsService, -// private readonly vacationLeaveService: VacationLeaveRequestsService, -// private readonly vacationLogic: VacationService, -// private readonly leaveUtils: LeaveRequestsUtils, -// private readonly emailResolver: EmailToIdResolver, -// private readonly typeResolver: BankCodesResolver, -// ) {} + // handle distribution to the right service according to the selected type and action + async handle(dto: UpsertLeaveRequestDto): Promise { + switch (dto.type) { + case LeaveTypes.HOLIDAY: + if( dto.action === 'create'){ + // return this.holidayService.create(dto); + } else if (dto.action === 'update') { + return this.update(dto, LeaveTypes.HOLIDAY); + } else if (dto.action === 'delete'){ + return this.delete(dto, LeaveTypes.HOLIDAY); + } + case LeaveTypes.VACATION: + if( dto.action === 'create'){ + // return this.vacationService.create(dto); + } else if (dto.action === 'update') { + return this.update(dto, LeaveTypes.VACATION); + } else if (dto.action === 'delete'){ + return this.delete(dto, LeaveTypes.VACATION); + } + case LeaveTypes.SICK: + if( dto.action === 'create'){ + // return this.sickLeaveService.create(dto); + } else if (dto.action === 'update') { + return this.update(dto, LeaveTypes.SICK); + } else if (dto.action === 'delete'){ + return this.delete(dto, LeaveTypes.SICK); + } + default: + throw new BadRequestException(`Unsupported leave type: ${dto.type} or action: ${dto.action}`); + } + } -// //handle distribution to the right service according to the selected type and action -// async handle(dto: UpsertLeaveRequestDto): Promise { -// switch (dto.type) { -// case LeaveTypes.HOLIDAY: -// if( dto.action === 'create'){ -// return this.holidayLeaveService.create(dto); -// } else if (dto.action === 'update') { -// return this.update(dto, LeaveTypes.HOLIDAY); -// } else if (dto.action === 'delete'){ -// return this.delete(dto, LeaveTypes.HOLIDAY); -// } -// case LeaveTypes.VACATION: -// if( dto.action === 'create'){ -// return this.vacationLeaveService.create(dto); -// } else if (dto.action === 'update') { -// return this.update(dto, LeaveTypes.VACATION); -// } else if (dto.action === 'delete'){ -// return this.delete(dto, LeaveTypes.VACATION); -// } -// case LeaveTypes.SICK: -// if( dto.action === 'create'){ -// return this.sickLeaveService.create(dto); -// } else if (dto.action === 'update') { -// return this.update(dto, LeaveTypes.SICK); -// } else if (dto.action === 'delete'){ -// return this.delete(dto, LeaveTypes.SICK); -// } -// default: -// throw new BadRequestException(`Unsupported leave type: ${dto.type} or action: ${dto.action}`); -// } -// } + async delete(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise { + const email = dto.email.trim(); + const dates = normalizeDates(dto.dates); + const employee_id = await this.emailResolver.findIdByEmail(email); + if (!dates.length) throw new BadRequestException("Dates array must not be empty"); -// async delete(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise { -// const email = dto.email.trim(); -// const dates = normalizeDates(dto.dates); -// const employee_id = await this.emailResolver.findIdByEmail(email); -// if (!dates.length) throw new BadRequestException("Dates array must not be empty"); + const rows = await this.prisma.leaveRequests.findMany({ + where: { + employee_id: employee_id, + leave_type: type, + date: { in: dates.map((d) => toDateOnly(d)) }, + }, + select: leaveRequestsSelect, + }); -// const rows = await this.prisma.leaveRequests.findMany({ -// where: { -// employee_id: employee_id, -// leave_type: type, -// date: { in: dates.map((d) => toDateOnly(d)) }, -// }, -// select: leaveRequestsSelect, -// }); + if (rows.length !== dates.length) { + const missing = dates.filter((isoDate) => !rows.some((row) => toISODateKey(row.date) === isoDate)); + throw new NotFoundException(`No Leave request found for: ${missing.join(", ")}`); + } -// if (rows.length !== dates.length) { -// const missing = dates.filter((isoDate) => !rows.some((row) => toISODateKey(row.date) === isoDate)); -// throw new NotFoundException(`No Leave request found for: ${missing.join(", ")}`); -// } + for (const row of rows) { + if (row.approval_status === LeaveApprovalStatus.APPROVED) { + const iso = toISODateKey(row.date); + await this.leaveUtils.removeShift(email, employee_id, iso, type); + } + } -// for (const row of rows) { -// if (row.approval_status === LeaveApprovalStatus.APPROVED) { -// const iso = toISODateKey(row.date); -// await this.leaveUtils.removeShift(email, employee_id, iso, type); -// } -// } + await this.prisma.leaveRequests.deleteMany({ + where: { id: { in: rows.map((row) => row.id) } }, + }); -// await this.prisma.leaveRequests.deleteMany({ -// where: { id: { in: rows.map((row) => row.id) } }, -// }); + const deleted = rows.map((row) => ({ ...mapRowToView(row), action: "delete" as const })); + return { action: "delete", leave_requests: deleted }; + } -// const deleted = rows.map((row) => ({ ...mapRowToView(row), action: "delete" as const })); -// return { action: "delete", leave_requests: deleted }; -// } + async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise { + const email = dto.email.trim(); + const employee_id = await this.emailResolver.findIdByEmail(email); + const bank_code = await this.typeResolver.findByType(type); + if(!bank_code) throw new NotFoundException(`bank_code not found`); + const modifier = Number(bank_code.modifier ?? 1); + const dates = normalizeDates(dto.dates); + if (!dates.length) { + throw new BadRequestException("Dates array must not be empty"); + } -// async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise { -// const email = dto.email.trim(); -// const employee_id = await this.emailResolver.findIdByEmail(email); -// const bank_code = await this.typeResolver.findByType(type); -// if(!bank_code) throw new NotFoundException(`bank_code not found`); -// const modifier = Number(bank_code.modifier ?? 1); -// const dates = normalizeDates(dto.dates); -// if (!dates.length) { -// throw new BadRequestException("Dates array must not be empty"); -// } + const entries = await Promise.all( + dates.map(async (iso_date) => { + const date = toDateOnly(iso_date); + const existing = await this.prisma.leaveRequests.findUnique({ + where: { + leave_per_employee_date: { + employee_id: employee_id, + leave_type: type, + date, + }, + }, + select: leaveRequestsSelect, + }); + if (!existing) throw new NotFoundException(`No Leave request found for ${iso_date}`); + return { iso_date, date, existing }; + }), + ); -// const entries = await Promise.all( -// dates.map(async (iso_date) => { -// const date = toDateOnly(iso_date); -// const existing = await this.prisma.leaveRequests.findUnique({ -// where: { -// leave_per_employee_date: { -// employee_id: employee_id, -// leave_type: type, -// date, -// }, -// }, -// select: leaveRequestsSelect, -// }); -// if (!existing) throw new NotFoundException(`No Leave request found for ${iso_date}`); -// return { iso_date, date, existing }; -// }), -// ); + const updated: LeaveRequestViewDto[] = []; -// const updated: LeaveRequestViewDto[] = []; + if (type === LeaveTypes.SICK) { + const firstExisting = entries[0].existing; + const fallbackRequested = + firstExisting.requested_hours !== null && firstExisting.requested_hours !== undefined + ? Number(firstExisting.requested_hours) + : 8; + const requested_hours_per_day = dto.requested_hours ?? fallbackRequested; + const reference_date = entries.reduce( + (latest, entry) => (entry.date > latest ? entry.date : latest), + entries[0].date, + ); + const total_payable_hours = await this.sickLogic.calculateSickLeavePay( + employee_id, + reference_date, + entries.length, + requested_hours_per_day, + modifier, + ); + let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours)); + const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier); -// if (type === LeaveTypes.SICK) { -// const firstExisting = entries[0].existing; -// const fallbackRequested = -// firstExisting.requested_hours !== null && firstExisting.requested_hours !== undefined -// ? Number(firstExisting.requested_hours) -// : 8; -// const requested_hours_per_day = dto.requested_hours ?? fallbackRequested; -// const reference_date = entries.reduce( -// (latest, entry) => (entry.date > latest ? entry.date : latest), -// entries[0].date, -// ); -// const total_payable_hours = await this.sickLogic.calculateSickLeavePay( -// employee_id, -// reference_date, -// entries.length, -// requested_hours_per_day, -// modifier, -// ); -// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours)); -// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier); + for (const { iso_date, existing } of entries) { + const previous_status = existing.approval_status; + const payable = Math.min(remaining_payable_hours, daily_payable_cap); + const payable_rounded = roundToQuarterHour(Math.max(0, payable)); + remaining_payable_hours = roundToQuarterHour( + Math.max(0, remaining_payable_hours - payable_rounded), + ); -// for (const { iso_date, existing } of entries) { -// const previous_status = existing.approval_status; -// const payable = Math.min(remaining_payable_hours, daily_payable_cap); -// const payable_rounded = roundToQuarterHour(Math.max(0, payable)); -// remaining_payable_hours = roundToQuarterHour( -// Math.max(0, remaining_payable_hours - payable_rounded), -// ); + const row = await this.prisma.leaveRequests.update({ + where: { id: existing.id }, + data: { + comment: dto.comment ?? existing.comment, + requested_hours: requested_hours_per_day, + payable_hours: payable_rounded, + bank_code_id: bank_code.id, + approval_status: dto.approval_status ?? existing.approval_status, + }, + select: leaveRequestsSelect, + }); -// const row = await this.prisma.leaveRequests.update({ -// where: { id: existing.id }, -// data: { -// comment: dto.comment ?? existing.comment, -// requested_hours: requested_hours_per_day, -// payable_hours: payable_rounded, -// bank_code_id: bank_code.id, -// approval_status: dto.approval_status ?? existing.approval_status, -// }, -// select: leaveRequestsSelect, -// }); + const was_approved = previous_status === LeaveApprovalStatus.APPROVED; + const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED; + const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); -// const was_approved = previous_status === LeaveApprovalStatus.APPROVED; -// const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED; -// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); + if (!was_approved && is_approved) { + await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); + } else if (was_approved && !is_approved) { + await this.leaveUtils.removeShift(email, employee_id, iso_date, type); + } else if (was_approved && is_approved) { + await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); + } + updated.push({ ...mapRowToView(row), action: "update" }); + } + return { action: "update", leave_requests: updated }; + } -// if (!was_approved && is_approved) { -// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); -// } else if (was_approved && !is_approved) { -// await this.leaveUtils.removeShift(email, employee_id, iso_date, type); -// } else if (was_approved && is_approved) { -// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); -// } -// updated.push({ ...mapRowToView(row), action: "update" }); -// } -// return { action: "update", leave_requests: updated }; -// } + for (const { iso_date, date, existing } of entries) { + const previous_status = existing.approval_status; + const fallbackRequested = + existing.requested_hours !== null && existing.requested_hours !== undefined + ? Number(existing.requested_hours) + : 8; + const requested_hours = dto.requested_hours ?? fallbackRequested; -// for (const { iso_date, date, existing } of entries) { -// const previous_status = existing.approval_status; -// const fallbackRequested = -// existing.requested_hours !== null && existing.requested_hours !== undefined -// ? Number(existing.requested_hours) -// : 8; -// const requested_hours = dto.requested_hours ?? fallbackRequested; + let payable: number; + switch (type) { + case LeaveTypes.HOLIDAY: + payable = await this.holidayService.calculateHolidayPay(email, date, modifier); + break; + case LeaveTypes.VACATION: { + const days_requested = requested_hours / 8; + payable = await this.vacationLogic.calculateVacationPay( + employee_id, + date, + Math.max(0, days_requested), + modifier, + ); + break; + } + default: + payable = existing.payable_hours !== null && existing.payable_hours !== undefined + ? Number(existing.payable_hours) + : requested_hours; + } -// let payable: number; -// switch (type) { -// case LeaveTypes.HOLIDAY: -// payable = await this.holidayService.calculateHolidayPay(email, date, modifier); -// break; -// case LeaveTypes.VACATION: { -// const days_requested = requested_hours / 8; -// payable = await this.vacationLogic.calculateVacationPay( -// employee_id, -// date, -// Math.max(0, days_requested), -// modifier, -// ); -// break; -// } -// default: -// payable = existing.payable_hours !== null && existing.payable_hours !== undefined -// ? Number(existing.payable_hours) -// : requested_hours; -// } + const row = await this.prisma.leaveRequests.update({ + where: { id: existing.id }, + data: { + requested_hours, + comment: dto.comment ?? existing.comment, + payable_hours: payable, + bank_code_id: bank_code.id, + approval_status: dto.approval_status ?? existing.approval_status, + }, + select: leaveRequestsSelect, + }); -// const row = await this.prisma.leaveRequests.update({ -// where: { id: existing.id }, -// data: { -// requested_hours, -// comment: dto.comment ?? existing.comment, -// payable_hours: payable, -// bank_code_id: bank_code.id, -// approval_status: dto.approval_status ?? existing.approval_status, -// }, -// select: leaveRequestsSelect, -// }); + const was_approved = previous_status === LeaveApprovalStatus.APPROVED; + const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED; + const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); -// const was_approved = previous_status === LeaveApprovalStatus.APPROVED; -// const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED; -// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); - -// if (!was_approved && is_approved) { -// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); -// } else if (was_approved && !is_approved) { -// await this.leaveUtils.removeShift(email, employee_id, iso_date, type); -// } else if (was_approved && is_approved) { -// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); -// } -// updated.push({ ...mapRowToView(row), action: "update" }); -// } -// return { action: "update", leave_requests: updated }; -// } -// } + if (!was_approved && is_approved) { + await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); + } else if (was_approved && !is_approved) { + await this.leaveUtils.removeShift(email, employee_id, iso_date, type); + } else if (was_approved && is_approved) { + await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment); + } + updated.push({ ...mapRowToView(row), action: "update" }); + } + return { action: "update", leave_requests: updated }; + } +} diff --git a/src/time-and-attendance/modules/leave-requests/services/sick-leave-requests.service.ts b/src/time-and-attendance/modules/leave-requests/services/sick-leave-requests.service.ts index a4554b1..145a283 100644 --- a/src/time-and-attendance/modules/leave-requests/services/sick-leave-requests.service.ts +++ b/src/time-and-attendance/modules/leave-requests/services/sick-leave-requests.service.ts @@ -1,98 +1,99 @@ -// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto"; -// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; -// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; -// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client"; -// import { leaveRequestsSelect } from "../utils/leave-requests.select"; -// import { mapRowToView } from "../mappers/leave-requests.mapper"; -// import { PrismaService } from "src/prisma/prisma.service"; -// import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service"; -// import { roundToQuarterHour } from "src/common/utils/date-utils"; -// import { LeaveRequestsUtils } from "../utils/leave-request.util"; -// import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers"; -// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils"; -// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils"; +import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; +import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client"; +import { roundToQuarterHour } from "src/common/utils/date-utils"; +import { PrismaService } from "src/prisma/prisma.service"; +import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service"; +import { LeaveRequestViewDto } from "src/time-and-attendance/modules/leave-requests/dtos/leave-request-view.dto"; +import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/modules/leave-requests/dtos/upsert-leave-request.dto"; +import { mapRowToView } from "src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper"; +import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util"; +import { leaveRequestsSelect } from "src/time-and-attendance/modules/leave-requests/utils/leave-requests.select"; +import { normalizeDates, toDateOnly } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers"; +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"; -// @Injectable() -// export class SickLeaveRequestsService { -// constructor( -// private readonly prisma: PrismaService, -// private readonly sickService: SickLeaveService, -// private readonly leaveUtils: LeaveRequestsUtils, -// private readonly emailResolver: EmailToIdResolver, -// private readonly typeResolver: BankCodesResolver, -// ) {} -// async create(dto: UpsertLeaveRequestDto): Promise { -// const email = dto.email.trim(); -// const employee_id = await this.emailResolver.findIdByEmail(email); -// const bank_code = await this.typeResolver.findByType(LeaveTypes.SICK); -// if(!bank_code) throw new NotFoundException(`bank_code not found`); +@Injectable() +export class SickLeaveRequestsService { + constructor( + private readonly prisma: PrismaService, + private readonly sickService: SickLeaveService, + private readonly leaveUtils: LeaveRequestsUtils, + private readonly emailResolver: EmailToIdResolver, + private readonly typeResolver: BankCodesResolver, + ) {} + + async create(dto: UpsertLeaveRequestDto): Promise { + const email = dto.email.trim(); + const employee_id = await this.emailResolver.findIdByEmail(email); + const bank_code = await this.typeResolver.findByType(LeaveTypes.SICK); + if(!bank_code) throw new NotFoundException(`bank_code not found`); -// const modifier = bank_code.modifier ?? 1; -// const dates = normalizeDates(dto.dates); -// if (!dates.length) throw new BadRequestException("Dates array must not be empty"); -// const requested_hours_per_day = dto.requested_hours ?? 8; + const modifier = bank_code.modifier ?? 1; + const dates = normalizeDates(dto.dates); + if (!dates.length) throw new BadRequestException("Dates array must not be empty"); + const requested_hours_per_day = dto.requested_hours ?? 8; -// const entries = dates.map((iso) => ({ iso, date: toDateOnly(iso) })); -// const reference_date = entries.reduce( -// (latest, entry) => (entry.date > latest ? entry.date : latest), -// entries[0].date, -// ); -// const total_payable_hours = await this.sickService.calculateSickLeavePay( -// employee_id, -// reference_date, -// entries.length, -// requested_hours_per_day, -// modifier, -// ); -// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours)); -// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier); + const entries = dates.map((iso) => ({ iso, date: toDateOnly(iso) })); + const reference_date = entries.reduce( + (latest, entry) => (entry.date > latest ? entry.date : latest), + entries[0].date, + ); + const total_payable_hours = await this.sickService.calculateSickLeavePay( + employee_id, + reference_date, + entries.length, + requested_hours_per_day, + modifier, + ); + let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours)); + const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier); -// const created: LeaveRequestViewDto[] = []; + const created: LeaveRequestViewDto[] = []; -// for (const { iso, date } of entries) { -// const existing = await this.prisma.leaveRequests.findUnique({ -// where: { -// leave_per_employee_date: { -// employee_id: employee_id, -// leave_type: LeaveTypes.SICK, -// date, -// }, -// }, -// select: { id: true }, -// }); -// if (existing) { -// throw new BadRequestException(`Sick request already exists for ${iso}`); -// } + for (const { iso, date } of entries) { + const existing = await this.prisma.leaveRequests.findUnique({ + where: { + leave_per_employee_date: { + employee_id: employee_id, + leave_type: LeaveTypes.SICK, + date, + }, + }, + select: { id: true }, + }); + if (existing) { + throw new BadRequestException(`Sick request already exists for ${iso}`); + } -// const payable = Math.min(remaining_payable_hours, daily_payable_cap); -// const payable_rounded = roundToQuarterHour(Math.max(0, payable)); -// remaining_payable_hours = roundToQuarterHour( -// Math.max(0, remaining_payable_hours - payable_rounded), -// ); + const payable = Math.min(remaining_payable_hours, daily_payable_cap); + const payable_rounded = roundToQuarterHour(Math.max(0, payable)); + remaining_payable_hours = roundToQuarterHour( + Math.max(0, remaining_payable_hours - payable_rounded), + ); -// const row = await this.prisma.leaveRequests.create({ -// data: { -// employee_id: employee_id, -// bank_code_id: bank_code.id, -// leave_type: LeaveTypes.SICK, -// comment: dto.comment ?? "", -// requested_hours: requested_hours_per_day, -// payable_hours: payable_rounded, -// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING, -// date, -// }, -// select: leaveRequestsSelect, -// }); + const row = await this.prisma.leaveRequests.create({ + data: { + employee_id: employee_id, + bank_code_id: bank_code.id, + leave_type: LeaveTypes.SICK, + comment: dto.comment ?? "", + requested_hours: requested_hours_per_day, + payable_hours: payable_rounded, + approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING, + date, + }, + select: leaveRequestsSelect, + }); -// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); -// if (row.approval_status === LeaveApprovalStatus.APPROVED) { -// await this.leaveUtils.syncShift(email, employee_id, iso, hours,LeaveTypes.SICK, row.comment); -// } + const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); + if (row.approval_status === LeaveApprovalStatus.APPROVED) { + // await this.leaveUtils.syncShift(email, employee_id, iso, hours,LeaveTypes.SICK, row.comment); + } -// created.push({ ...mapRowToView(row), action: "create" }); -// } + created.push({ ...mapRowToView(row), action: "create" }); + } -// return { action: "create", leave_requests: created }; -// } -// } + return { action: "create", leave_requests: created }; + } +} diff --git a/src/time-and-attendance/modules/leave-requests/services/vacation-leave-requests.service.ts b/src/time-and-attendance/modules/leave-requests/services/vacation-leave-requests.service.ts index 34223f5..652feff 100644 --- a/src/time-and-attendance/modules/leave-requests/services/vacation-leave-requests.service.ts +++ b/src/time-and-attendance/modules/leave-requests/services/vacation-leave-requests.service.ts @@ -1,93 +1,93 @@ - -// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto"; -// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; -// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; -// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client"; -// import { VacationService } from "src/modules/business-logics/services/vacation.service"; -// import { PrismaService } from "src/prisma/prisma.service"; -// import { mapRowToView } from "../mappers/leave-requests.mapper"; -// import { leaveRequestsSelect } from "../utils/leave-requests.select"; -// import { roundToQuarterHour } from "src/common/utils/date-utils"; -// import { LeaveRequestsUtils } from "../utils/leave-request.util"; -// import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers"; -// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils"; -// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils"; +import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; +import { LeaveTypes, LeaveApprovalStatus } from "@prisma/client"; +import { roundToQuarterHour } from "src/common/utils/date-utils"; +import { PrismaService } from "src/prisma/prisma.service"; +import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; +import { LeaveRequestViewDto } from "src/time-and-attendance/modules/leave-requests/dtos/leave-request-view.dto"; +import { UpsertLeaveRequestDto, UpsertResult } from "src/time-and-attendance/modules/leave-requests/dtos/upsert-leave-request.dto"; +import { mapRowToView } from "src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper"; +import { LeaveRequestsUtils } from "src/time-and-attendance/modules/leave-requests/utils/leave-request.util"; +import { leaveRequestsSelect } from "src/time-and-attendance/modules/leave-requests/utils/leave-requests.select"; +import { normalizeDates, toDateOnly } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers"; +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"; -// @Injectable() -// export class VacationLeaveRequestsService { -// constructor( -// private readonly prisma: PrismaService, -// private readonly vacationService: VacationService, -// private readonly leaveUtils: LeaveRequestsUtils, -// private readonly emailResolver: EmailToIdResolver, -// private readonly typeResolver: BankCodesResolver, -// ) {} -// async create(dto: UpsertLeaveRequestDto): Promise { -// const email = dto.email.trim(); -// const employee_id = await this.emailResolver.findIdByEmail(email); -// const bank_code = await this.typeResolver.findByType(LeaveTypes.VACATION); -// if(!bank_code) throw new NotFoundException(`bank_code not found`); +@Injectable() +export class VacationLeaveRequestsService { + constructor( + private readonly prisma: PrismaService, + private readonly vacationService: VacationService, + private readonly leaveUtils: LeaveRequestsUtils, + private readonly emailResolver: EmailToIdResolver, + private readonly typeResolver: BankCodesResolver, + ) {} + + async create(dto: UpsertLeaveRequestDto): Promise { + const email = dto.email.trim(); + const employee_id = await this.emailResolver.findIdByEmail(email); + const bank_code = await this.typeResolver.findByType(LeaveTypes.VACATION); + if(!bank_code) throw new NotFoundException(`bank_code not found`); -// const modifier = bank_code.modifier ?? 1; -// const dates = normalizeDates(dto.dates); -// const requested_hours_per_day = dto.requested_hours ?? 8; -// if (!dates.length) throw new BadRequestException("Dates array must not be empty"); + const modifier = bank_code.modifier ?? 1; + const dates = normalizeDates(dto.dates); + const requested_hours_per_day = dto.requested_hours ?? 8; + if (!dates.length) throw new BadRequestException("Dates array must not be empty"); -// const entries = dates -// .map((iso) => ({ iso, date: toDateOnly(iso) })) -// .sort((a, b) => a.date.getTime() - b.date.getTime()); -// const start_date = entries[0].date; -// const total_payable_hours = await this.vacationService.calculateVacationPay( -// employee_id, -// start_date, -// entries.length, -// modifier, -// ); -// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours)); -// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier); + const entries = dates + .map((iso) => ({ iso, date: toDateOnly(iso) })) + .sort((a, b) => a.date.getTime() - b.date.getTime()); + const start_date = entries[0].date; + const total_payable_hours = await this.vacationService.calculateVacationPay( + employee_id, + start_date, + entries.length, + modifier, + ); + let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours)); + const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier); -// const created: LeaveRequestViewDto[] = []; + const created: LeaveRequestViewDto[] = []; -// for (const { iso, date } of entries) { -// const existing = await this.prisma.leaveRequests.findUnique({ -// where: { -// leave_per_employee_date: { -// employee_id: employee_id, -// leave_type: LeaveTypes.VACATION, -// date, -// }, -// }, -// select: { id: true }, -// }); -// if (existing) throw new BadRequestException(`Vacation request already exists for ${iso}`); + for (const { iso, date } of entries) { + const existing = await this.prisma.leaveRequests.findUnique({ + where: { + leave_per_employee_date: { + employee_id: employee_id, + leave_type: LeaveTypes.VACATION, + date, + }, + }, + select: { id: true }, + }); + if (existing) throw new BadRequestException(`Vacation request already exists for ${iso}`); -// const payable = Math.min(remaining_payable_hours, daily_payable_cap); -// const payable_rounded = roundToQuarterHour(Math.max(0, payable)); -// remaining_payable_hours = roundToQuarterHour( -// Math.max(0, remaining_payable_hours - payable_rounded), -// ); + const payable = Math.min(remaining_payable_hours, daily_payable_cap); + const payable_rounded = roundToQuarterHour(Math.max(0, payable)); + remaining_payable_hours = roundToQuarterHour( + Math.max(0, remaining_payable_hours - payable_rounded), + ); -// const row = await this.prisma.leaveRequests.create({ -// data: { -// employee_id: employee_id, -// bank_code_id: bank_code.id, -// payable_hours: payable_rounded, -// requested_hours: requested_hours_per_day, -// leave_type: LeaveTypes.VACATION, -// comment: dto.comment ?? "", -// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING, -// date, -// }, -// select: leaveRequestsSelect, -// }); + const row = await this.prisma.leaveRequests.create({ + data: { + employee_id: employee_id, + bank_code_id: bank_code.id, + payable_hours: payable_rounded, + requested_hours: requested_hours_per_day, + leave_type: LeaveTypes.VACATION, + comment: dto.comment ?? "", + approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING, + date, + }, + select: leaveRequestsSelect, + }); -// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); -// if (row.approval_status === LeaveApprovalStatus.APPROVED) { -// await this.leaveUtils.syncShift(email, employee_id, iso, hours, LeaveTypes.VACATION, row.comment); -// } -// created.push({ ...mapRowToView(row), action: "create" }); -// } -// return { action: "create", leave_requests: created }; -// } -// } + const hours = Number(row.payable_hours ?? row.requested_hours ?? 0); + if (row.approval_status === LeaveApprovalStatus.APPROVED) { + // await this.leaveUtils.syncShift(email, employee_id, iso, hours, LeaveTypes.VACATION, row.comment); + } + created.push({ ...mapRowToView(row), action: "create" }); + } + return { action: "create", leave_requests: created }; + } +} diff --git a/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts b/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts index d01ccf2..3b40236 100644 --- a/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts +++ b/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts @@ -1,104 +1,106 @@ -// import { hhmmFromLocal, toDateOnly, toStringFromDate } from "src/modules/shared/helpers/date-time.helpers"; -// import { BadRequestException, Injectable } from "@nestjs/common"; -// import { PrismaService } from "src/prisma/prisma.service"; -// import { LeaveTypes } from "@prisma/client"; -// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types"; -// @Injectable() -// export class LeaveRequestsUtils { -// constructor( -// private readonly prisma: PrismaService, -// private readonly shiftsCommand: ShiftsCommandService, -// ){} +import { BadRequestException, Injectable } from "@nestjs/common"; +import { PrismaService } from "src/prisma/prisma.service"; +import { LeaveTypes } from "@prisma/client"; +import { toDateOnly, toStringFromDate, hhmmFromLocal } 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 { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service"; -// async syncShift( -// email: string, -// employee_id: number, -// date: string, -// hours: number, -// type: LeaveTypes, -// comment?: string, -// ) { -// if (hours <= 0) return; +@Injectable() +export class LeaveRequestsUtils { + constructor( + private readonly prisma: PrismaService, + private readonly shiftsService: ShiftsUpsertService, + ){} -// const duration_minutes = Math.round(hours * 60); -// if (duration_minutes > 8 * 60) { -// throw new BadRequestException("Amount of hours cannot exceed 8 hours per day."); -// } -// const date_only = toDateOnly(date); -// const yyyy_mm_dd = toStringFromDate(date_only); + async syncShift( + email: string, + employee_id: number, + date: string, + hours: number, + type: LeaveTypes, + comment?: string, + ) { + if (hours <= 0) return; + + const duration_minutes = Math.round(hours * 60); + if (duration_minutes > 8 * 60) { + throw new BadRequestException("Amount of hours cannot exceed 8 hours per day."); + } + const date_only = toDateOnly(date); + const yyyy_mm_dd = toStringFromDate(date_only); -// const start_minutes = 8 * 60; -// const end_minutes = start_minutes + duration_minutes; -// const toHHmm = (total: number) => -// `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`; + const start_minutes = 8 * 60; + const end_minutes = start_minutes + duration_minutes; + const toHHmm = (total: number) => + `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`; -// const existing = await this.prisma.shifts.findFirst({ -// where: { -// date: date_only, -// bank_code: { type }, -// timesheet: { employee_id: employee_id }, -// }, -// include: { bank_code: true }, -// }); + const existing = await this.prisma.shifts.findFirst({ + where: { + date: date_only, + bank_code: { type }, + timesheet: { employee_id: employee_id }, + }, + include: { bank_code: true }, + }); -// const action: UpsertAction = existing ? 'update' : 'create'; + const action: UpsertAction = existing ? 'update' : 'create'; -// await this.shiftsCommand.upsertShifts(email, action, { -// old_shift: existing -// ? { -// date: yyyy_mm_dd, -// start_time: existing.start_time.toISOString().slice(11, 16), -// end_time: existing.end_time.toISOString().slice(11, 16), -// type: existing.bank_code?.type ?? type, -// is_remote: existing.is_remote, -// is_approved:existing.is_approved, -// comment: existing.comment ?? undefined, -// } -// : undefined, -// new_shift: { -// date: yyyy_mm_dd, -// start_time: toHHmm(start_minutes), -// end_time: toHHmm(end_minutes), -// is_remote: existing?.is_remote ?? false, -// is_approved:existing?.is_approved ?? false, -// comment: comment ?? existing?.comment ?? "", -// type: type, -// }, -// }); -// } + // await this.shiftsService.upsertShifts(email, action, { + // old_shift: existing + // ? { + // date: yyyy_mm_dd, + // start_time: existing.start_time.toISOString().slice(11, 16), + // end_time: existing.end_time.toISOString().slice(11, 16), + // type: existing.bank_code?.type ?? type, + // is_remote: existing.is_remote, + // is_approved:existing.is_approved, + // comment: existing.comment ?? undefined, + // } + // : undefined, + // new_shift: { + // date: yyyy_mm_dd, + // start_time: toHHmm(start_minutes), + // end_time: toHHmm(end_minutes), + // is_remote: existing?.is_remote ?? false, + // is_approved:existing?.is_approved ?? false, + // comment: comment ?? existing?.comment ?? "", + // type: type, + // }, + // }); + } -// async removeShift( -// email: string, -// employee_id: number, -// iso_date: string, -// type: LeaveTypes, -// ) { -// const date_only = toDateOnly(iso_date); -// const yyyy_mm_dd = toStringFromDate(date_only); -// const existing = await this.prisma.shifts.findFirst({ -// where: { -// date: date_only, -// bank_code: { type }, -// timesheet: { employee_id: employee_id }, -// }, -// include: { bank_code: true }, -// }); -// if (!existing) return; + async removeShift( + email: string, + employee_id: number, + iso_date: string, + type: LeaveTypes, + ) { + const date_only = toDateOnly(iso_date); + const yyyy_mm_dd = toStringFromDate(date_only); + const existing = await this.prisma.shifts.findFirst({ + where: { + date: date_only, + bank_code: { type }, + timesheet: { employee_id: employee_id }, + }, + include: { bank_code: true }, + }); + if (!existing) return; -// await this.shiftsCommand.upsertShifts(email, 'delete', { -// old_shift: { -// date: yyyy_mm_dd, -// start_time: hhmmFromLocal(existing.start_time), -// end_time: hhmmFromLocal(existing.end_time), -// type: existing.bank_code?.type ?? type, -// is_remote: existing.is_remote, -// is_approved:existing.is_approved, -// comment: existing.comment ?? undefined, -// }, -// }); -// } + // await this.shiftsService.upsertShifts(email, 'delete', { + // old_shift: { + // date: yyyy_mm_dd, + // start_time: hhmmFromLocal(existing.start_time), + // end_time: hhmmFromLocal(existing.end_time), + // type: existing.bank_code?.type ?? type, + // is_remote: existing.is_remote, + // is_approved:existing.is_approved, + // comment: existing.comment ?? undefined, + // }, + // }); + } -// } +} diff --git a/src/time-and-attendance/time-and-attendance.module.ts b/src/time-and-attendance/time-and-attendance.module.ts index ebcbd05..e3eff36 100644 --- a/src/time-and-attendance/time-and-attendance.module.ts +++ b/src/time-and-attendance/time-and-attendance.module.ts @@ -1,20 +1,20 @@ -import { Module } from "@nestjs/common"; -import { ExpensesArchiveController } from "src/modules/archival/controllers/expenses-archive.controller"; -import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module"; -import { ExpenseController } from "src/time-and-attendance/modules/expenses/controllers/expense.controller"; -import { ExpenseUpsertService } from "src/time-and-attendance/modules/expenses/services/expense-upsert.service"; -import { ExpensesArchivalService } from "src/time-and-attendance/modules/expenses/services/expenses-archival.service"; -import { PayperiodsModule } from "src/time-and-attendance/modules/pay-period/pay-periods.module"; -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 { SchedulePresetsController } from "src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller"; import { SchedulePresetsCommandService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service"; +import { GetTimesheetsOverviewService } from "src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service"; import { SchedulePresetsQueryService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service"; -import { ShiftController } from "src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller"; -import { ShiftsGetService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service"; +import { SchedulePresetsController } from "src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller"; +import { ExpensesArchivalService } from "src/time-and-attendance/modules/expenses/services/expenses-archival.service"; +// import { LeaveRequestController } from "src/time-and-attendance/modules/leave-requests/controllers/leave-requests.controller"; +import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module"; +import { ExpenseUpsertService } from "src/time-and-attendance/modules/expenses/services/expense-upsert.service"; import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service"; import { TimesheetController } from "src/time-and-attendance/modules/time-tracker/timesheets/controllers/timesheet.controller"; -import { GetTimesheetsOverviewService } from "src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service"; +import { ExpenseController } from "src/time-and-attendance/modules/expenses/controllers/expense.controller"; +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 { PayperiodsModule } from "src/time-and-attendance/modules/pay-period/pay-periods.module"; +import { ShiftsGetService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service"; +import { ShiftController } from "src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller"; +import { Module } from "@nestjs/common"; @Module({ imports: [BusinessLogicsModule, PayperiodsModule], @@ -23,7 +23,6 @@ import { GetTimesheetsOverviewService } from "src/time-and-attendance/modules/ti ShiftController, SchedulePresetsController, ExpenseController, - ExpensesArchiveController, // LeaveRequestController, ],