refactor(EsLint): EsLint corrections
This commit is contained in:
parent
37a4da7923
commit
aa72651a67
|
|
@ -1,6 +1,6 @@
|
|||
// @ts-check
|
||||
import eslint from '@eslint/js';
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
// import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ export default tseslint.config(
|
|||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
eslintPluginPrettierRecommended,
|
||||
// eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Controller, Get } from '@nestjs/common';
|
||||
import { Controller } from '@nestjs/common';
|
||||
|
||||
@Controller()
|
||||
export class AppController { }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { BadRequestException, Module, ValidationPipe } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { NotificationsModule } from './shared/notifications/notifications.module';
|
||||
import { PrismaPostgresModule } from '../prisma/postgres/prisma-postgres.module';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
|
@ -21,7 +20,6 @@ import { CustomerSupportModule } from 'src/customer-support/customer-support.mod
|
|||
AuthenticationModule,
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
ScheduleModule.forRoot(), //cronjobs
|
||||
NotificationsModule,
|
||||
PrismaPostgresModule,
|
||||
PrismaMariadbModule,
|
||||
PrismaLegacyModule,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,14 @@ import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/
|
|||
|
||||
@Controller('chatbot')
|
||||
export class ChatbotController {
|
||||
constructor(private readonly chatbotService: ChatbotService) {}
|
||||
constructor(private readonly chatbotService: ChatbotService) { }
|
||||
|
||||
@Post('')
|
||||
@ModuleAccessAllowed(ModulesEnum.chatbot)
|
||||
async testConnection(@Body() body: UserMessageDto, @Access('email') email: string): Promise<Message> {
|
||||
async testConnection(
|
||||
@Body() body: UserMessageDto,
|
||||
@Access('email') email: string,
|
||||
): Promise<Message> {
|
||||
return await this.chatbotService.pingExternalApi(body, email);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,14 @@ import { HttpModule } from '@nestjs/axios';
|
|||
import { ChatbotService } from 'src/chatbot/chatbot.service';
|
||||
|
||||
@Module({
|
||||
imports: [HttpModule],
|
||||
controllers: [ChatbotController],
|
||||
providers: [ChatbotService],
|
||||
exports: [],
|
||||
imports: [
|
||||
HttpModule,
|
||||
],
|
||||
controllers: [
|
||||
ChatbotController,
|
||||
],
|
||||
providers: [
|
||||
ChatbotService,
|
||||
],
|
||||
})
|
||||
export class ChatbotModule {}
|
||||
export class ChatbotModule { }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { UserMessageDto } from 'src/chatbot/dtos/user-message.dto';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { PageContextDto } from 'src/chatbot/dtos/page-context.dto';
|
||||
import { ChatbotResponseDto, UserMessageDto } from 'src/chatbot/dtos/user-message.dto';
|
||||
import { Message } from 'src/chatbot/dtos/dialog-message.dto';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -14,41 +13,11 @@ export class ChatbotService {
|
|||
const { data } = await firstValueFrom(this.httpService.post(
|
||||
'https://n8nai.targo.ca/webhook/chatty-Mcbot',
|
||||
{ userInput: body.userInput, userId: email, sessionId: this.sessionId, pageContext: body.pageContext ?? undefined }
|
||||
));
|
||||
)) as ChatbotResponseDto;
|
||||
|
||||
return {
|
||||
text: data[0].output,
|
||||
sent: false,
|
||||
};
|
||||
}
|
||||
|
||||
// async sendPageContext(body: PageContextDto, email: string) {
|
||||
// const { data } = await firstValueFrom(
|
||||
// this.httpService.post(
|
||||
// 'https://n8nai.targo.ca/webhook/chatty-Mcbot',
|
||||
// { features: body, userId: email, userInput: '' },
|
||||
// ),
|
||||
// );
|
||||
// return data;
|
||||
// }
|
||||
|
||||
// Will have to modify later on to accomodate newer versions of User Auth/User type Structure
|
||||
// async sendUserContext(user_email: string) {
|
||||
// if (!this.sessionId) {
|
||||
// this.sessionId = 'SessionId = ' + user_email;
|
||||
// }
|
||||
|
||||
// const response = await firstValueFrom(
|
||||
// this.httpService.post(
|
||||
// 'https://n8nai.targo.ca/webhook/chatty-Mcbot',
|
||||
// {
|
||||
// userId: this.sessionId,
|
||||
// userInput: '',
|
||||
// features: '',
|
||||
// },
|
||||
// { headers: { 'Content-Tyoe': 'application/json' } },
|
||||
// ),
|
||||
// );
|
||||
// return response.data;
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { IsBoolean, IsString } from 'class-validator';
|
||||
|
||||
export class Message {
|
||||
@IsString()
|
||||
text!: string;
|
||||
|
||||
@IsBoolean()
|
||||
sent!: boolean;
|
||||
@IsString() text: string;
|
||||
@IsBoolean() sent: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
import { IsArray, IsString } from 'class-validator';
|
||||
import { IsArray, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class PageContextDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@IsArray()
|
||||
features: string[];
|
||||
|
||||
@IsString()
|
||||
path?: string;
|
||||
@IsString() name: string;
|
||||
@IsString() description: string;
|
||||
@IsArray() features: string[];
|
||||
@IsString() @IsOptional() path?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
import { Transform, Type } from 'class-transformer';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { PageContextDto } from './page-context.dto';
|
||||
|
||||
export class UserMessageDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Transform(({ value }) => value.trim())
|
||||
userInput!: string;
|
||||
@IsString() userInput: string;
|
||||
@IsOptional() @Type(() => PageContextDto) pageContext?: PageContextDto | undefined;
|
||||
}
|
||||
|
||||
export class ChatbotResponseDto {
|
||||
@Type(() => ChatbotOutput) data: ChatbotOutput[];
|
||||
}
|
||||
|
||||
export class ChatbotOutput {
|
||||
@IsString() output: string;
|
||||
}
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
||||
import { UserDto } from "src/identity-and-account/users-management/user.dto";
|
||||
|
||||
export const Access = createParamDecorator(
|
||||
(data:string, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
user: UserDto;
|
||||
}
|
||||
|
||||
export const Access = createParamDecorator<keyof UserDto | undefined>(
|
||||
(data, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest<AuthenticatedRequest>();
|
||||
const user: UserDto = request.user;
|
||||
return data ? user?.[data] : user;
|
||||
},
|
||||
);
|
||||
|
|
@ -7,13 +7,7 @@ import {
|
|||
import { Reflector } from '@nestjs/core';
|
||||
import { MODULES_KEY } from '../decorators/modules-guard.decorators';
|
||||
import { Modules } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||
|
||||
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
// TODO: Create an actual user model based on OAuth signin
|
||||
user: any;
|
||||
}
|
||||
import { AuthenticatedRequest } from 'src/common/decorators/module-access.decorators';
|
||||
|
||||
@Injectable()
|
||||
export class ModulesGuard implements CanActivate {
|
||||
|
|
@ -27,7 +21,7 @@ export class ModulesGuard implements CanActivate {
|
|||
if (!requiredModules || requiredModules.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const request = ctx.switchToHttp().getRequest<RequestWithUser>();
|
||||
const request = ctx.switchToHttp().getRequest<AuthenticatedRequest>();
|
||||
const user = request.user;
|
||||
|
||||
if (!user) {
|
||||
|
|
@ -36,7 +30,7 @@ export class ModulesGuard implements CanActivate {
|
|||
for (const module of requiredModules) {
|
||||
if (!user.user_module_access.includes(module)) {
|
||||
throw new ForbiddenException(
|
||||
`This account does not have required access to: ${module}. current user modules: ${user.user_module_access} , required modules: ${requiredModules}`,
|
||||
`This account does not have required access to: ${module}. current user modules: ${user.user_module_access.toString()} , required modules: ${requiredModules.toString()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { Prisma, PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
|
||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
|
|
@ -25,7 +25,9 @@ export class BankCodesResolver {
|
|||
};
|
||||
|
||||
//finds only id by type
|
||||
readonly findBankCodeIDByType = async (type: string, client?: Tx
|
||||
readonly findBankCodeIDByType = async (
|
||||
type: string,
|
||||
client?: Tx
|
||||
): Promise<Result<number, string>> => {
|
||||
const db = (client ?? this.prisma) as PrismaClient;
|
||||
const bank_code = await db.bankCodes.findFirst({
|
||||
|
|
@ -37,7 +39,9 @@ export class BankCodesResolver {
|
|||
return { success: true, data: bank_code.id };
|
||||
}
|
||||
|
||||
readonly findTypeByBankCodeId = async (bank_code_id: number, client?: Tx
|
||||
readonly findTypeByBankCodeId = async (
|
||||
bank_code_id: number,
|
||||
client?: Tx
|
||||
): Promise<Result<string, string>> => {
|
||||
const db = (client ?? this.prisma) as PrismaClient;
|
||||
const bank_code = await db.bankCodes.findFirst({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||
import { Prisma, PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
|
||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
|
|
@ -12,7 +12,9 @@ export class EmailToIdResolver {
|
|||
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
// find employee_id using email
|
||||
readonly findIdByEmail = async (email: string, client?: Tx
|
||||
readonly findIdByEmail = async (
|
||||
email: string,
|
||||
client?: Tx
|
||||
): Promise<Result<number, string>> => {
|
||||
const db = (client ?? this.prisma) as PrismaClient;
|
||||
const employee = await db.employees.findFirst({
|
||||
|
|
@ -24,7 +26,9 @@ export class EmailToIdResolver {
|
|||
}
|
||||
|
||||
// find user_id using email
|
||||
readonly resolveUserIdWithEmail = async (email: string, client?: Tx
|
||||
readonly resolveUserIdWithEmail = async (
|
||||
email: string,
|
||||
client?: Tx
|
||||
): Promise<Result<string, string>> => {
|
||||
const db = (client ?? this.prisma) as PrismaClient;
|
||||
const user = await db.users.findFirst({
|
||||
|
|
@ -34,6 +38,4 @@ export class EmailToIdResolver {
|
|||
if (!user) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
|
||||
return { success: true, data: user.id };
|
||||
}
|
||||
|
||||
readonly findFullNameByEmail
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||
import { Prisma, PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
|
||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
|
|
@ -9,7 +9,10 @@ type Tx = Prisma.TransactionClient | PrismaClient;
|
|||
export class FullNameResolver {
|
||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
readonly resolveFullName = async (employee_id: number, client?: Tx): Promise<Result<string, string>> => {
|
||||
readonly resolveFullName = async (
|
||||
employee_id: number,
|
||||
client?: Tx
|
||||
): Promise<Result<string, string>> => {
|
||||
const db = (client ?? this.prisma) as PrismaClient;
|
||||
const employee = await db.employees.findUnique({
|
||||
where: { id: employee_id },
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ interface ShiftKey {
|
|||
export class ShiftIdResolver {
|
||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
readonly findShiftIdByData = async (key: ShiftKey, client?: Tx
|
||||
readonly findShiftIdByData = async (
|
||||
key: ShiftKey,
|
||||
client?: Tx
|
||||
): Promise<Result<number, string>> => {
|
||||
const db = (client ?? this.prisma) as PrismaClient;
|
||||
const shift = await db.shifts.findFirst({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||
import { EmailToIdResolver } from "./email-id.mapper";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { weekStartSunday } from "src/common/utils/date-utils";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||
import { PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/internal/class";
|
||||
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||
|
||||
|
|
@ -16,7 +16,11 @@ export class EmployeeTimesheetResolver {
|
|||
private readonly emailResolver: EmailToIdResolver,
|
||||
) { }
|
||||
|
||||
readonly findTimesheetIdByEmail = async (email: string, date: Date, client?: Tx): Promise<Result<{ id: number }, string>> => {
|
||||
readonly findTimesheetIdByEmail = async (
|
||||
email: string,
|
||||
date: Date,
|
||||
client?: Tx
|
||||
): Promise<Result<{ id: number }, string>> => {
|
||||
const db = (client ?? this.prisma) as PrismaClient;
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
if (!employee_id.success) return { success: false, error: employee_id.error }
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ export abstract class BaseApprovalService<T> {
|
|||
protected abstract delegateFor(tx: TransactionClient): UpdatableDelegate<T>;
|
||||
|
||||
//standard update Aproval
|
||||
async updateApproval(id: number, is_approved: boolean): Promise<T> {
|
||||
async updateApproval(
|
||||
id: number,
|
||||
is_approved: boolean
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await this.delegate.update({
|
||||
where: { id },
|
||||
|
|
@ -34,7 +37,11 @@ export abstract class BaseApprovalService<T> {
|
|||
}
|
||||
}
|
||||
|
||||
async updateApprovalWithTransaction(tx: TransactionClient, id: number, is_approved: boolean): Promise<T> {
|
||||
async updateApprovalWithTransaction(
|
||||
tx: TransactionClient,
|
||||
id: number,
|
||||
is_approved: boolean
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await this.delegateFor(tx).update({
|
||||
where: { id },
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
//Prisma 'where' clause for DTO filters
|
||||
export function buildPrismaWhere<T extends Record<string, any>>(dto: T): Record <string, any> {
|
||||
const where: Record<string,any> = {};
|
||||
|
||||
for (const [key,value] of Object.entries(dto)) {
|
||||
if (value === undefined || value === null) continue;
|
||||
|
||||
if (key.endsWith('_contains')) {
|
||||
const field = key.slice(0, - '_contains'.length);
|
||||
where[field] = { constains: value };
|
||||
} else if (key === 'start_date' || key === 'end_date') {
|
||||
where.date = where.date || {};
|
||||
const op = key === 'start_date' ? 'gte' : 'lte';
|
||||
where.date[op] = new Date(value);
|
||||
} else {
|
||||
where[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// //This file is used to store function that help translate MariaDB data to Typescript manipulation requirements for the type "boolean".
|
||||
|
||||
// import { PhoneAddrEnhancedCapable } from "src/customer-support/dtos/phone.dto";
|
||||
|
||||
// //From MariaDB to Frontend
|
||||
// export const fromTinyIntToBoolean = async (tinyInt: number): Promise<boolean> => {
|
||||
// let booleanValue = true;
|
||||
// if ((tinyInt = 0) || (tinyInt = -1)) return booleanValue = false;
|
||||
// return booleanValue;
|
||||
// }
|
||||
|
||||
// //From Frontend to MariaDB TinyInt boolean 1 - 0
|
||||
// export const fromBooleanToTinyInt = async (boolean: boolean): Promise<number> => {
|
||||
// return boolean ? 1 : 0;
|
||||
// }
|
||||
|
||||
// //From Frontend to MariaDB TinyInt boolean -1 - 1
|
||||
// export const fromBooleanToTinyIntNegative = async (boolean: boolean): Promise<number> => {
|
||||
// return boolean ? 1 : -1;
|
||||
// }
|
||||
|
||||
// //From MariaDB to Frontend String boolean yes - no / Y - N / etc...
|
||||
// export const fromStringToBoolean = async (string: string): Promise<boolean> => {
|
||||
// let booleanValue = true;
|
||||
// let stringValue = string.toLowerCase();
|
||||
// if ((stringValue = "n") || (stringValue = "no") || (stringValue = "non")) {
|
||||
// return booleanValue = false;
|
||||
// }
|
||||
// return booleanValue;
|
||||
// }
|
||||
|
||||
// export const fromBooleanToEnum = async (boolean: boolean): Promise<PhoneAddrEnhancedCapable> => {
|
||||
// return boolean ? PhoneAddrEnhancedCapable.Y : PhoneAddrEnhancedCapable.N;
|
||||
// }
|
||||
|
||||
// export const fromEnumToBoolean = async (enumValue: PhoneAddrEnhancedCapable): Promise<boolean> => {
|
||||
// return enumValue ? true : false;
|
||||
// }
|
||||
|
|
@ -1 +0,0 @@
|
|||
//This file is used to store function that help translate MariaDB data to Typescript manipulation requirements for the type "number".
|
||||
|
|
@ -1 +0,0 @@
|
|||
//This file is used to store function that help translate MariaDB data to Typescript manipulation requirements for the type "string".
|
||||
|
|
@ -9,17 +9,23 @@ import { UsersService } from 'src/identity-and-account/users-management/services
|
|||
|
||||
|
||||
@Module({
|
||||
imports: [ PassportModule.register({
|
||||
imports: [
|
||||
PassportModule.register({
|
||||
session: true,
|
||||
defaultStrategy: 'openidconnect'
|
||||
}), UsersModule, ],
|
||||
}), UsersModule,
|
||||
],
|
||||
providers: [
|
||||
AuthentikAuthService,
|
||||
AuthentikStrategy,
|
||||
ExpressSessionSerializer,
|
||||
UsersService,
|
||||
],
|
||||
exports: [ AuthentikAuthService ],
|
||||
controllers: [AuthController],
|
||||
exports: [
|
||||
AuthentikAuthService
|
||||
],
|
||||
controllers: [
|
||||
AuthController
|
||||
],
|
||||
})
|
||||
export class AuthenticationModule {}
|
||||
export class AuthenticationModule { }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export class AuthController {
|
|||
|
||||
@Get('callback')
|
||||
@UseGuards(OIDCLoginGuard)
|
||||
loginCallback(@Req() req: Request, @Res() res: Response) {
|
||||
loginCallback(@Req() _req: Request, @Res() res: Response) {
|
||||
res.redirect(process.env.REDIRECT_URL_DEV!);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Request } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class OIDCLoginGuard extends AuthGuard('openidconnect') {
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const result = (await super.canActivate(context)) as boolean;
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
await super.logIn(request);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export class ExpressSessionSerializer extends PassportSerializer {
|
|||
}
|
||||
done(null, user);
|
||||
}
|
||||
deserializeUser(payload: any, done: (err: any, payload: string) => void): any {
|
||||
deserializeUser(payload: any, done: (err: any, payload: any) => void): any {
|
||||
if (!payload){
|
||||
done(new UnauthorizedException('Deserialize user error'), payload);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { UsersService } from 'src/identity-and-account/users-management/services/users.service';
|
||||
import { UserDto } from 'src/identity-and-account/users-management/user.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AuthentikAuthService {
|
||||
constructor(private usersService: UsersService) {}
|
||||
|
||||
async validateUser(user_email: string): Promise<any> {
|
||||
async validateUser(user_email: string): Promise<Partial<UserDto>> {
|
||||
const user = await this.usersService.findOneByEmail(user_email);
|
||||
|
||||
return user;
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidcon
|
|||
|
||||
async validate(
|
||||
_issuer: string,
|
||||
profile: Profile,
|
||||
_profile: Profile,
|
||||
_context: any,
|
||||
_idToken: string,
|
||||
idToken: string,
|
||||
_accessToken: string,
|
||||
_refreshToken: string,
|
||||
_params: any,
|
||||
|
|
@ -50,9 +50,9 @@ export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidcon
|
|||
try {
|
||||
|
||||
|
||||
const components = _idToken.split('.');
|
||||
const components = idToken.split('.');
|
||||
const payload = Buffer.from(components[1], "base64").toString('utf-8');
|
||||
const claims = JSON.parse(payload);
|
||||
const claims = JSON.parse(payload) as AuthentikPayload;
|
||||
|
||||
if (!claims.email) return cb(new Error('Missing email in OIDC profile'), false);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,13 +18,17 @@ export class EmployeesController {
|
|||
|
||||
@Get('personal-profile')
|
||||
@ModuleAccessAllowed(ModulesEnum.personal_profile)
|
||||
async findOwnProfile(@Access('email') email: string): Promise<Result<Partial<EmployeeDetailedDto>, string>> {
|
||||
async findOwnProfile(
|
||||
@Access('email') email: string
|
||||
): Promise<Result<Partial<EmployeeDetailedDto>, string>> {
|
||||
return await this.getService.findOwnProfile(email);
|
||||
}
|
||||
|
||||
@Get('profile')
|
||||
@ModuleAccessAllowed(ModulesEnum.personal_profile)
|
||||
async findProfile(@Access('email') email: string, @Query('employee_email') employee_email?: string,
|
||||
async findProfile(
|
||||
@Access('email') email: string,
|
||||
@Query('employee_email') employee_email?: string,
|
||||
): Promise<Result<Partial<EmployeeDetailedDto>, string>> {
|
||||
return await this.getService.findOneDetailedProfile(email, employee_email);
|
||||
}
|
||||
|
|
@ -37,13 +41,17 @@ export class EmployeesController {
|
|||
|
||||
@Post('create')
|
||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||
async createEmployee(@Body() dto: EmployeeDetailedUpsertDto): Promise<Result<boolean, string>> {
|
||||
async createEmployee(
|
||||
@Body() dto: EmployeeDetailedUpsertDto
|
||||
): Promise<Result<boolean, string>> {
|
||||
return await this.createService.createEmployee(dto);
|
||||
}
|
||||
|
||||
@Patch('update')
|
||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||
async updateEmployee(@Body() dto:EmployeeDetailedUpsertDto){
|
||||
async updateEmployee(
|
||||
@Body() dto:EmployeeDetailedUpsertDto
|
||||
){
|
||||
return await this.updateService.updateEmployee(dto);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import { EmployeesUpdateService } from 'src/identity-and-account/employees/servi
|
|||
import { EmployeesCreateService } from 'src/identity-and-account/employees/services/employees-create.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [EmployeesController],
|
||||
controllers: [
|
||||
EmployeesController
|
||||
],
|
||||
providers: [
|
||||
EmployeesGetService,
|
||||
EmployeesUpdateService,
|
||||
|
|
@ -16,6 +17,8 @@ import { EmployeesCreateService } from 'src/identity-and-account/employees/servi
|
|||
AccessGetService,
|
||||
EmailToIdResolver
|
||||
],
|
||||
exports: [EmployeesGetService],
|
||||
exports: [
|
||||
EmployeesGetService
|
||||
],
|
||||
})
|
||||
export class EmployeesModule { }
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ export class HomePageController {
|
|||
|
||||
@Get('help')
|
||||
@ModuleAccessAllowed(ModulesEnum.dashboard)
|
||||
async getIntroductionHelper(@Access('email') email: string) {
|
||||
async getIntroductionHelper(
|
||||
@Access('email') email: string
|
||||
) {
|
||||
return await this.homePageService.buildHomePageHelpMessage(email);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,15 @@ import { HomePageController } from "src/identity-and-account/help/help-page.cont
|
|||
import { HomePageService } from "src/identity-and-account/help/help-page.service";
|
||||
|
||||
@Module({
|
||||
controllers: [HomePageController],
|
||||
providers: [HomePageService, EmailToIdResolver],
|
||||
exports: [HomePageService],
|
||||
controllers: [
|
||||
HomePageController
|
||||
],
|
||||
providers: [
|
||||
HomePageService,
|
||||
EmailToIdResolver
|
||||
],
|
||||
exports: [
|
||||
HomePageService
|
||||
],
|
||||
})
|
||||
export class HomePageModule { };
|
||||
|
|
@ -10,7 +10,9 @@ export class HomePageService {
|
|||
private readonly emailresolver: EmailToIdResolver,
|
||||
) { }
|
||||
|
||||
buildHomePageHelpMessage = async (email: string): Promise<Result<string[], string>> => {
|
||||
buildHomePageHelpMessage = async (
|
||||
email: string
|
||||
): Promise<Result<string[], string>> => {
|
||||
const user_id = await this.emailresolver.resolveUserIdWithEmail(email);
|
||||
if (!user_id.success) return { success: false, error: 'INVALID_EMAIL' };
|
||||
|
||||
|
|
|
|||
|
|
@ -12,14 +12,19 @@ export class PreferencesController {
|
|||
|
||||
@Patch('update')
|
||||
@ModuleAccessAllowed(ModulesEnum.personal_profile)
|
||||
async updatePreferences(@Access('email') email: string, @Body() payload: PreferencesDto
|
||||
async updatePreferences(
|
||||
@Access('email') email: string,
|
||||
@Body() payload: PreferencesDto
|
||||
): Promise<Result<PreferencesDto, string>> {
|
||||
return this.service.updatePreferences(email, payload);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ModuleAccessAllowed(ModulesEnum.personal_profile)
|
||||
async findPreferences(@Access('email') email: string, @Query() employee_email?: string) {
|
||||
async findPreferences(
|
||||
@Access('email') email: string,
|
||||
@Query() employee_email?: string
|
||||
) {
|
||||
return this.service.findPreferences(email, employee_email);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,16 @@ import { PreferencesService } from "./preferences.service";
|
|||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
controllers: [ PreferencesController ],
|
||||
providers: [ PreferencesService, EmailToIdResolver ],
|
||||
exports: [ PreferencesService ],
|
||||
controllers: [
|
||||
PreferencesController
|
||||
],
|
||||
providers: [
|
||||
PreferencesService,
|
||||
EmailToIdResolver
|
||||
],
|
||||
exports: [
|
||||
PreferencesService
|
||||
],
|
||||
})
|
||||
|
||||
export class PreferencesModule {}
|
||||
|
|
@ -11,7 +11,10 @@ export class PreferencesService {
|
|||
private readonly emailResolver: EmailToIdResolver,
|
||||
) { }
|
||||
|
||||
async findPreferences(email: string, employee_email?: string): Promise<Result<PreferencesDto, string>> {
|
||||
async findPreferences(
|
||||
email: string,
|
||||
employee_email?: string
|
||||
): Promise<Result<PreferencesDto, string>> {
|
||||
const account_email = employee_email ?? email;
|
||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||
|
|
@ -42,7 +45,10 @@ export class PreferencesService {
|
|||
return { success: true, data: preferences };
|
||||
}
|
||||
|
||||
async updatePreferences(email: string, dto: PreferencesDto): Promise<Result<PreferencesDto, string>> {
|
||||
async updatePreferences(
|
||||
email: string,
|
||||
dto: PreferencesDto
|
||||
): Promise<Result<PreferencesDto, string>> {
|
||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
|
||||
if (!user_id.success) return { success: false, error: user_id.error }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { IsBoolean } from "class-validator";
|
||||
|
||||
export class ModuleAccess {
|
||||
@IsBoolean() timesheets!: boolean;
|
||||
@IsBoolean() timesheets_approval!: boolean;
|
||||
@IsBoolean() employee_list!: boolean;
|
||||
@IsBoolean() employee_management!: boolean;
|
||||
@IsBoolean() personal_profile!: boolean;
|
||||
@IsBoolean() dashboard!: boolean;
|
||||
@IsBoolean() timesheets: boolean;
|
||||
@IsBoolean() timesheets_approval: boolean;
|
||||
@IsBoolean() employee_list: boolean;
|
||||
@IsBoolean() employee_management: boolean;
|
||||
@IsBoolean() personal_profile: boolean;
|
||||
@IsBoolean() dashboard: boolean;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Body, Controller, Get, Patch, Query, Req } from "@nestjs/common";
|
||||
import { Body, Controller, Get, Patch, Query } from "@nestjs/common";
|
||||
import { Access } from "src/common/decorators/module-access.decorators";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { ModuleAccess } from "src/identity-and-account/user-module-access/module-acces.dto";
|
||||
|
|
@ -16,7 +16,9 @@ export class ModuleAccessController {
|
|||
|
||||
@Get()
|
||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||
async findAccess(@Access('email') email: string, @Query('employee_email') employee_email?: string
|
||||
async findAccess(
|
||||
@Access('email') email: string,
|
||||
@Query('employee_email') employee_email?: string
|
||||
): Promise<Result<boolean, string>> {
|
||||
await this.getService.findModuleAccess(email, employee_email);
|
||||
return { success: true, data: true };
|
||||
|
|
@ -24,7 +26,10 @@ export class ModuleAccessController {
|
|||
|
||||
@Patch('update')
|
||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||
async updateAccess(@Access('email') email: string, @Body() dto: ModuleAccess, @Query('employee_email') employee_email?: string
|
||||
async updateAccess(
|
||||
@Access('email') email: string,
|
||||
@Body() dto: ModuleAccess,
|
||||
@Query('employee_email') employee_email?: string
|
||||
): Promise<Result<boolean, string>> {
|
||||
await this.updateService.updateModuleAccess(email, dto, employee_email);
|
||||
return { success: true, data: true };
|
||||
|
|
|
|||
|
|
@ -5,8 +5,16 @@ import { AccessGetService } from "src/identity-and-account/user-module-access/se
|
|||
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||
|
||||
@Module({
|
||||
controllers: [ModuleAccessController],
|
||||
providers: [AccessUpdateService, AccessGetService, EmailToIdResolver],
|
||||
exports: [AccessGetService],
|
||||
controllers: [
|
||||
ModuleAccessController
|
||||
],
|
||||
providers: [
|
||||
AccessUpdateService,
|
||||
AccessGetService,
|
||||
EmailToIdResolver
|
||||
],
|
||||
exports: [
|
||||
AccessGetService
|
||||
],
|
||||
})
|
||||
export class ModuleAccessModule { }
|
||||
|
|
@ -12,7 +12,10 @@ export class AccessGetService {
|
|||
private readonly emailResolver: EmailToIdResolver,
|
||||
) { }
|
||||
|
||||
async findModuleAccess(email: string, employee_email?: string): Promise<Result<ModuleAccess, string>> {
|
||||
async findModuleAccess(
|
||||
email: string,
|
||||
employee_email?: string
|
||||
): Promise<Result<ModuleAccess, string>> {
|
||||
const account_email = employee_email ?? email;
|
||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ export class AccessUpdateService {
|
|||
private readonly emailResolver: EmailToIdResolver,
|
||||
) { }
|
||||
|
||||
async updateModuleAccess(email: string, dto: ModuleAccess, employee_email?: string): Promise<Result<ModuleAccess, string>> {
|
||||
async updateModuleAccess(
|
||||
email: string,
|
||||
dto: ModuleAccess,
|
||||
employee_email?: string
|
||||
): Promise<Result<ModuleAccess, string>> {
|
||||
const account_email = employee_email ?? email;
|
||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||
|
|
@ -52,7 +56,10 @@ export class AccessUpdateService {
|
|||
return { success: true, data: updated_access };
|
||||
}
|
||||
|
||||
async revokeModuleAccess(email: string, employee_email?: string): Promise<Result<ModuleAccess, string>> {
|
||||
async revokeModuleAccess(
|
||||
email: string,
|
||||
employee_email?: string
|
||||
): Promise<Result<ModuleAccess, string>> {
|
||||
const account_email = employee_email ?? email;
|
||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import { PrismaPostgresService } from 'prisma/postgres/prisma-postgres.service';
|
|||
export abstract class AbstractUserService {
|
||||
constructor(protected readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
async findOneByEmail(email: string): Promise<Partial<Users>> {
|
||||
async findOneByEmail(
|
||||
email: string
|
||||
): Promise<Partial<Users>> {
|
||||
const user = await this.prisma.users.findUnique({
|
||||
where: { email },
|
||||
include: {
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ export class UserDto {
|
|||
@IsString() last_name: string;
|
||||
@IsEmail() email: string;
|
||||
@IsEnum(Roles) role: string;
|
||||
@IsArray() @IsEnum(Modules, { each: true }) user_module_access!: Modules[];
|
||||
@IsArray() @IsEnum(Modules, { each: true }) user_module_access: Modules[];
|
||||
}
|
||||
|
|
@ -3,8 +3,14 @@ import { UsersService } from './services/users.service';
|
|||
import { PrismaPostgresModule } from 'prisma/postgres/prisma-postgres.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaPostgresModule],
|
||||
providers: [UsersService],
|
||||
exports: [UsersService],
|
||||
imports: [
|
||||
PrismaPostgresModule
|
||||
],
|
||||
providers: [
|
||||
UsersService
|
||||
],
|
||||
exports: [
|
||||
UsersService
|
||||
],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
|
|
|||
10
src/main.ts
10
src/main.ts
|
|
@ -1,8 +1,8 @@
|
|||
import 'reflect-metadata';
|
||||
import * as nodeCrypto from 'crypto';
|
||||
if (!(globalThis as any).crypto) {
|
||||
(globalThis as any).crypto = nodeCrypto;
|
||||
}
|
||||
// import * as nodeCrypto from 'crypto';
|
||||
// if (!(globalThis as any).crypto) {
|
||||
// (globalThis as any).crypto = nodeCrypto;
|
||||
// }
|
||||
import { NestFactory, Reflector } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ModulesGuard } from './common/guards/modules.guard';
|
||||
|
|
@ -14,7 +14,7 @@ import { PrismaPostgresService } from 'prisma/postgres/prisma-postgres.service';
|
|||
const SESSION_TOKEN_DURATION_MINUTES = 180
|
||||
|
||||
async function bootstrap() {
|
||||
(BigInt.prototype as any).toJSON = function () { return Number(this) };
|
||||
BigInt.prototype['toJSON'] = function () { return Number(this) };
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const prisma_postgres = app.get(PrismaPostgresService);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
// import { Module } from "@nestjs/common";
|
||||
// import { ScheduleModule } from "@nestjs/schedule";
|
||||
// import { TimesheetsModule } from "../timesheets/timesheets.module";
|
||||
// import { ExpensesModule } from "../expenses/expenses.module";
|
||||
// import { ShiftsModule } from "../shifts/shifts.module";
|
||||
// import { LeaveRequestsModule } from "../leave-requests/leave-requests.module";
|
||||
// import { ArchivalService } from "./services/archival.service";
|
||||
// import { EmployeesArchiveController } from "./controllers/employees-archive.controller";
|
||||
// import { ExpensesArchiveController } from "./controllers/expenses-archive.controller";
|
||||
// import { LeaveRequestsArchiveController } from "./controllers/leave-requests-archive.controller";
|
||||
// import { ShiftsArchiveController } from "./controllers/shifts-archive.controller";
|
||||
// import { TimesheetsArchiveController } from "./controllers/timesheets-archive.controller";
|
||||
// import { EmployeesModule } from "../employees/employees.module";
|
||||
|
||||
// @Module({
|
||||
// imports: [
|
||||
// EmployeesModule,
|
||||
// ScheduleModule,
|
||||
// TimesheetsModule,
|
||||
// ExpensesModule,
|
||||
// ShiftsModule,
|
||||
// LeaveRequestsModule,
|
||||
// ],
|
||||
// providers: [ArchivalService],
|
||||
// controllers: [
|
||||
// EmployeesArchiveController,
|
||||
// ExpensesArchiveController,
|
||||
// LeaveRequestsArchiveController,
|
||||
// ShiftsArchiveController,
|
||||
// TimesheetsArchiveController,
|
||||
// ],
|
||||
// })
|
||||
|
||||
// export class ArchivalModule {}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// import { TimesheetArchiveService } from "src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-archive.service";
|
||||
// import { ExpensesArchivalService } from "src/time-and-attendance/modules/expenses/services/expenses-archival.service";
|
||||
// import { ShiftsArchivalService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service";
|
||||
// import { Injectable, Logger } from "@nestjs/common";
|
||||
// import { Cron } from "@nestjs/schedule";
|
||||
|
||||
// @Injectable()
|
||||
// export class ArchivalService {
|
||||
// private readonly logger = new Logger(ArchivalService.name);
|
||||
|
||||
// constructor(
|
||||
// private readonly timesheetsService: TimesheetArchiveService,
|
||||
// private readonly expensesService: ExpensesArchivalService,
|
||||
// private readonly shiftsService: ShiftsArchivalService,
|
||||
// ) {}
|
||||
|
||||
// @Cron('0 0 3 * * 1', {timeZone:'America/Toronto'}) // chaque premier lundi du mois à 03h00
|
||||
// async handleMonthlyArchival() {
|
||||
// const today = new Date();
|
||||
// const dayOfMonth = today.getDate();
|
||||
|
||||
// if (dayOfMonth > 7) {
|
||||
// this.logger.warn('Archive {awaiting 1st monday of the month for archivation process}')
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.logger.log('monthly archivation in process');
|
||||
// try {
|
||||
// await this.timesheetsService.archiveOld();
|
||||
// await this.expensesService.archiveOld();
|
||||
// await this.shiftsService.archiveOld();
|
||||
// // await this.leaveRequestsService.archiveExpired();
|
||||
// this.logger.log('archivation process done');
|
||||
// } catch (err) {
|
||||
// this.logger.error('an error occured during archivation process ', err);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
// import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } from "@nestjs/common";
|
||||
// import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { EmployeesArchivalService } from "src/modules/employees/services/employees-archival.service";
|
||||
|
||||
// @ApiTags('Employee Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/employees')
|
||||
// export class EmployeesArchiveController {
|
||||
// constructor(private readonly employeesArchiveService: EmployeesArchivalService) {}
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived employees'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived employees', isArray: true })
|
||||
// async findAllArchived(): Promise<EmployeesArchive[]> {
|
||||
// return this.employeesArchiveService.findAllArchived();
|
||||
// }
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR,RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch employee in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived employee found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<EmployeesArchive> {
|
||||
// try{
|
||||
// return await this.employeesArchiveService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived employee #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// import { UseGuards, Controller, Get, Param, ParseIntPipe, NotFoundException } from "@nestjs/common";
|
||||
// import { ApiTags, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { ExpensesArchivalService } from "src/time-and-attendance/modules/expenses/services/expenses-archival.service";
|
||||
|
||||
// @ApiTags('Expense Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/expenses')
|
||||
// export class ExpensesArchiveController {
|
||||
// constructor(private readonly expensesService: ExpensesArchivalService) {}
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived expenses'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived expenses', isArray: true })
|
||||
// async findAllArchived(): Promise<ExpensesArchive[]> {
|
||||
// return this.expensesService.findAllArchived();
|
||||
// }
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch expense in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived expense found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<ExpensesArchive> {
|
||||
// try{
|
||||
// return await this.expensesService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived expense #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// import { Controller } from '@nestjs/common';
|
||||
// import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
// @ApiTags('LeaveRequests Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/leaveRequests')
|
||||
// export class LeaveRequestsArchiveController {}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// import { Get, Param, ParseIntPipe, NotFoundException, Controller, UseGuards } from "@nestjs/common";
|
||||
// import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { ShiftsArchivalService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service";
|
||||
|
||||
// @ApiTags('Shift Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/shifts')
|
||||
// export class ShiftsArchiveController {
|
||||
// constructor(private readonly shiftsService: ShiftsArchivalService) {}
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived shifts'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived shifts', isArray: true })
|
||||
// async findAllArchived(): Promise<ShiftsArchive[]> {
|
||||
// return this.shiftsService.findAllArchived();
|
||||
// }
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR,RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch shift in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived shift found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<ShiftsArchive> {
|
||||
// try{
|
||||
// return await this.shiftsService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived shift #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
// import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } from "@nestjs/common";
|
||||
// import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { TimesheetArchiveService } from "src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-archive.service";
|
||||
|
||||
// @ApiTags('Timesheet Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/timesheets')
|
||||
// export class TimesheetsArchiveController {
|
||||
// constructor(private readonly timesheetsService: TimesheetArchiveService) {}
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived timesheets'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived timesheets', isArray: true })
|
||||
// async findAllArchived(): Promise<TimesheetsArchive[]> {
|
||||
// return this.timesheetsService.findAllArchived();
|
||||
// }
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch timesheet in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived timesheet found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<TimesheetsArchive> {
|
||||
// try{
|
||||
// return await this.timesheetsService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived timesheet #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export const NOTIF_TYPES = {
|
||||
SHIFT_OVERTIME_DAILY: 'shift.overtime.daily',
|
||||
|
||||
} as const;
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
export type NotificationCard = {
|
||||
type: string;
|
||||
message: string;
|
||||
severity?: 'info'|'warn'|'error';
|
||||
icon?: string;
|
||||
link?: string;
|
||||
meta?: Record<string, any>
|
||||
ts: string; //new Date().toISOString()
|
||||
};
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { Controller, Get, Req, Sse,
|
||||
MessageEvent as NestMessageEvent } from "@nestjs/common";
|
||||
import { Observable } from "rxjs";
|
||||
import { map } from 'rxjs/operators';
|
||||
import { NotificationsService } from "src/shared/notifications/notifications.service";
|
||||
|
||||
@Controller('notifications')
|
||||
export class NotificationsController {
|
||||
constructor(private readonly notificationsService: NotificationsService) {}
|
||||
|
||||
@Get('summary')
|
||||
async summary(@Req() req) {
|
||||
return this.notificationsService.summary(String(req.user.id));
|
||||
}
|
||||
|
||||
@Sse('stream')
|
||||
stream(@Req() req): Observable<NestMessageEvent> {
|
||||
const userId = String(req.user.id);
|
||||
return this.notificationsService.stream(userId).pipe(map((data): NestMessageEvent => ({ data })))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { NotificationsService } from "./notifications.service";
|
||||
import { NotificationsController } from "src/shared/notifications/notifications.controller";
|
||||
@Module({
|
||||
providers: [NotificationsService],
|
||||
controllers: [NotificationsController],
|
||||
exports: [NotificationsService],
|
||||
})
|
||||
export class NotificationsModule {}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { Subject } from "rxjs";
|
||||
import { NotificationCard } from "./notification.types";
|
||||
|
||||
@Injectable()
|
||||
export class NotificationsService {
|
||||
private readonly logger = new Logger(NotificationsService.name);
|
||||
|
||||
//Server-Sent Events FLUX and a buffer per user
|
||||
private streams = new Map<string, Subject<NotificationCard>>();
|
||||
private buffers = new Map<string, NotificationCard[]>();
|
||||
private readonly BUFFER_MAX = Number(process.env.NOTIF_BUFFER_MAX ?? 50);
|
||||
|
||||
private getOrCreateStream(user_id: string): Subject<NotificationCard> {
|
||||
let stream = this.streams.get(user_id);
|
||||
if (!stream){
|
||||
stream = new Subject<NotificationCard>();
|
||||
this.streams.set(user_id, stream);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
private getOrCreateBuffer(user_id: string){
|
||||
let buffer = this.buffers.get(user_id);
|
||||
if(!buffer) {
|
||||
buffer = [];
|
||||
this.buffers.set(user_id, buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//in-app pushes and keep a small history
|
||||
notify(user_id: string, card: NotificationCard) {
|
||||
const buffer = this.getOrCreateBuffer(user_id);
|
||||
buffer.unshift(card);
|
||||
if (buffer.length > this.BUFFER_MAX) {
|
||||
buffer.length = this.BUFFER_MAX;
|
||||
}
|
||||
this.getOrCreateStream(user_id).next(card);
|
||||
this.logger.debug(`Notification in-app => user: ${user_id} (${card.type})`);
|
||||
}
|
||||
|
||||
//SSE flux for current user
|
||||
stream(user_id: string) {
|
||||
return this.getOrCreateStream(user_id).asObservable();
|
||||
}
|
||||
|
||||
//return a summary of notifications kept in memory
|
||||
async summary(user_id: string): Promise<NotificationCard[]> {
|
||||
return this.getOrCreateBuffer(user_id);
|
||||
}
|
||||
|
||||
//clear buffers from memory
|
||||
clear(user_id: string) {
|
||||
this.buffers.set(user_id, []);
|
||||
}
|
||||
|
||||
onModuleDestroy() {
|
||||
for (const stream of this.streams.values()) stream.complete();
|
||||
this.streams.clear();
|
||||
this.buffers.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,10 @@ import { BankCodesService } from "src/time-and-attendance/bank-codes/bank-codes.
|
|||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||
export class BankCodesControllers {
|
||||
constructor(private readonly bankCodesService: BankCodesService) { }
|
||||
//_____________________________________________________________________________________________
|
||||
// Deprecated or unused methods
|
||||
//_____________________________________________________________________________________________
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: Prisma.BankCodesCreateInput
|
||||
create(
|
||||
@Body() dto: Prisma.BankCodesCreateInput
|
||||
): Promise<Result<boolean, string>> {
|
||||
return this.bankCodesService.create(dto);
|
||||
}
|
||||
|
|
@ -24,7 +22,9 @@ export class BankCodesControllers {
|
|||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id', ParseIntPipe) id: number, @Body() dto: Prisma.BankCodesUpdateInput
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: Prisma.BankCodesUpdateInput
|
||||
): Promise<Result<boolean, string>> {
|
||||
return this.bankCodesService.update(id, dto)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@ import { BankCodesControllers } from "src/time-and-attendance/bank-codes/bank-co
|
|||
import { BankCodesService } from "src/time-and-attendance/bank-codes/bank-codes.service";
|
||||
|
||||
@Module({
|
||||
controllers: [BankCodesControllers],
|
||||
providers: [BankCodesService, PrismaPostgresService],
|
||||
controllers: [
|
||||
BankCodesControllers
|
||||
],
|
||||
providers: [
|
||||
BankCodesService,
|
||||
PrismaPostgresService
|
||||
],
|
||||
})
|
||||
|
||||
export class BankCodesModule {}
|
||||
|
|
@ -7,7 +7,9 @@ import { Result } from "src/common/errors/result-error.factory";
|
|||
export class BankCodesService {
|
||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
async create(dto: Prisma.BankCodesCreateInput): Promise<Result<boolean, string>> {
|
||||
async create(
|
||||
dto: Prisma.BankCodesCreateInput
|
||||
): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
await this.prisma.bankCodes.create({
|
||||
data: {
|
||||
|
|
@ -27,7 +29,10 @@ export class BankCodesService {
|
|||
return this.prisma.bankCodes.findMany();
|
||||
}
|
||||
|
||||
async update(id: number, dto: Prisma.BankCodesUpdateInput): Promise<Result<boolean, string>> {
|
||||
async update(
|
||||
id: number,
|
||||
dto: Prisma.BankCodesUpdateInput
|
||||
): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
await this.prisma.bankCodes.update({
|
||||
where: { id },
|
||||
|
|
@ -44,7 +49,9 @@ export class BankCodesService {
|
|||
}
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<Result<boolean, string>> {
|
||||
async delete(
|
||||
id: number
|
||||
): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
await this.prisma.bankCodes.delete({
|
||||
where: { id },
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { Module } from "@nestjs/common";
|
|||
|
||||
|
||||
@Module({
|
||||
imports:[],
|
||||
providers: [
|
||||
HolidayService,
|
||||
MileageService,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ export class BankedHoursService {
|
|||
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
//manage shifts with bank_code.type BANKING
|
||||
manageBankingHours = async (employee_id: number, asked_hours: number, type: string): Promise<Result<number, string>> => {
|
||||
manageBankingHours = async (
|
||||
employee_id: number,
|
||||
asked_hours: number,
|
||||
type: string
|
||||
): Promise<Result<number, string>> => {
|
||||
if (asked_hours <= 0) return { success: false, error: 'INVALID_BANKING_HOURS' };
|
||||
|
||||
try {
|
||||
|
|
@ -50,8 +54,6 @@ export class BankedHoursService {
|
|||
} else if (type === 'WITHDRAW_BANKED') {
|
||||
if (asked_hours > banked_hours) {
|
||||
return { success: true, data: banked_hours } as Result<number, string>;
|
||||
} else {
|
||||
|
||||
}
|
||||
await tx.paidTimeOff.update({
|
||||
where: { employee_id: employee.id },
|
||||
|
|
@ -69,7 +71,8 @@ export class BankedHoursService {
|
|||
return result;
|
||||
|
||||
} catch (error) {
|
||||
return { success: false, error: 'INVALID_BANKING_SHIFT' };
|
||||
console.error(error);
|
||||
return { success: false, error: 'INVALID_BANKING_SHIFT: ' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,11 @@ export class HolidayService {
|
|||
*
|
||||
* @returns Average daily hours worked in the past four weeks as a `number`, to a maximum of `8`
|
||||
*/
|
||||
private async computeHoursPrevious4Weeks(external_payroll_id: number, company_code: number, holiday_date: Date): Promise<Result<number, string>> {
|
||||
private async computeHoursPrevious4Weeks(
|
||||
external_payroll_id: number,
|
||||
company_code: number,
|
||||
holiday_date: Date
|
||||
): Promise<Result<number, string>> {
|
||||
try {
|
||||
const valid_codes = ['G1', 'G43', 'G56', 'G104', 'G105', 'G305', 'G700', 'G720'];
|
||||
const holiday_week_start = getWeekStart(holiday_date);
|
||||
|
|
@ -42,7 +46,7 @@ export class HolidayService {
|
|||
});
|
||||
|
||||
if (!employee)
|
||||
return {success: false, error: 'EMPLOYEE_NOT_FOUND'};
|
||||
return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||
|
||||
const shifts = await this.prisma.shifts.findMany({
|
||||
where: {
|
||||
|
|
@ -76,11 +80,16 @@ export class HolidayService {
|
|||
const average_daily_hours = capped_total / 20;
|
||||
return { success: true, data: average_daily_hours };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { success: false, error: `an error occureded during holiday calculation` }
|
||||
}
|
||||
}
|
||||
|
||||
async calculateHolidayPay(employeePayrollID: number, companyCode: number, holiday_date: Date): Promise<Result<number, string>> {
|
||||
async calculateHolidayPay(
|
||||
employeePayrollID: number,
|
||||
companyCode: number,
|
||||
holiday_date: Date
|
||||
): Promise<Result<number, string>> {
|
||||
const average_daily_hours = await this.computeHoursPrevious4Weeks(employeePayrollID, companyCode, holiday_date);
|
||||
|
||||
if (!average_daily_hours.success) return { success: false, error: average_daily_hours.error };
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ export class MileageService {
|
|||
|
||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
public async calculateReimbursement(amount: number, bank_code_id: number): Promise<Result<number, string>> {
|
||||
public async calculateReimbursement(
|
||||
amount: number,
|
||||
bank_code_id: number
|
||||
): Promise<Result<number, string>> {
|
||||
if (amount < 0) return { success: false, error: 'The amount must be higher than 0' };
|
||||
|
||||
//fetch modifier
|
||||
|
|
|
|||
|
|
@ -27,11 +27,21 @@ type WeekOvertimeSummary = {
|
|||
@Injectable()
|
||||
export class OvertimeService {
|
||||
|
||||
private INCLUDED_TYPES = ['EMERGENCY', 'EVENING', 'OVERTIME', 'REGULAR', 'HOLIDAY', 'BANKING'] as const; // included types for weekly overtime calculation
|
||||
|
||||
private INCLUDED_TYPES = [
|
||||
'EMERGENCY',
|
||||
'EVENING',
|
||||
'OVERTIME',
|
||||
'REGULAR',
|
||||
'HOLIDAY',
|
||||
'BANKING'
|
||||
] as const;
|
||||
constructor(private prisma: PrismaPostgresService) { }
|
||||
|
||||
async getWeekOvertimeSummary(timesheet_id: number, date: Date, tx?: Tx): Promise<Result<WeekOvertimeSummary, string>> {
|
||||
async getWeekOvertimeSummary(
|
||||
timesheet_id: number,
|
||||
date: Date,
|
||||
tx?: Tx
|
||||
): Promise<Result<WeekOvertimeSummary, string>> {
|
||||
const db = (tx ?? this.prisma) as PrismaClient;
|
||||
|
||||
const week_start = getWeekStart(date);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import { Result } from "src/common/errors/result-error.factory";
|
|||
export class SickLeaveService {
|
||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||
|
||||
async updateSickLeaveHours(employee_id: number): Promise<Result<boolean, string>> {
|
||||
async updateSickLeaveHours(
|
||||
employee_id: number
|
||||
): Promise<Result<boolean, string>> {
|
||||
const THIRTY_DAYS = 1000 * 60 * 60 * 24 * 30;
|
||||
const FOURTEEN_DAYS = 1000 * 60 * 60 * 24 * 14;
|
||||
const today = new Date();
|
||||
|
|
@ -51,7 +53,7 @@ export class SickLeaveService {
|
|||
if (today.getTime() - employee.first_work_day.getTime() >= THIRTY_DAYS && employee.first_work_day.toISOString() === pto_details?.last_updated?.toISOString()) {
|
||||
const updated_pto = await this.addHoursToPTO(3 * 8, employee.id, today);
|
||||
|
||||
if (!updated_pto.success) return { success: updated_pto.success, error: updated_pto.error }
|
||||
if (!updated_pto.success) return { success: updated_pto.success, error: '' + updated_pto.error }
|
||||
}
|
||||
|
||||
const year_difference = today.getFullYear() - (pto_details!.last_updated?.getFullYear() ?? today.getFullYear());
|
||||
|
|
@ -69,7 +71,10 @@ export class SickLeaveService {
|
|||
}
|
||||
|
||||
// create a new PTO row
|
||||
async createNewPTORow(employee_id: number, today: Date): Promise<Result<Prisma.Result<typeof this.prisma.paidTimeOff, Prisma.PaidTimeOffDefaultArgs, 'findUnique' | 'create'>, string>> {
|
||||
async createNewPTORow(
|
||||
employee_id: number,
|
||||
today: Date
|
||||
): Promise<Result<Prisma.Result<typeof this.prisma.paidTimeOff, Prisma.PaidTimeOffDefaultArgs, 'findUnique' | 'create'>, string>> {
|
||||
try {
|
||||
const new_pto_entry = await this.prisma.paidTimeOff.create({
|
||||
data: {
|
||||
|
|
@ -88,12 +93,16 @@ export class SickLeaveService {
|
|||
|
||||
return { success: true, data: new_pto_entry };
|
||||
} catch (error) {
|
||||
return { success: false, error };
|
||||
return { success: false, error: '' + error };
|
||||
}
|
||||
}
|
||||
|
||||
// add n number of sick PTO hours to employee PTO
|
||||
async addHoursToPTO(sick_hours: number, employee_id: number, last_updated: Date) {
|
||||
async addHoursToPTO(
|
||||
sick_hours: number,
|
||||
employee_id: number,
|
||||
last_updated: Date
|
||||
) {
|
||||
try {
|
||||
const update_pto = await this.prisma.paidTimeOff.update({
|
||||
where: {
|
||||
|
|
@ -107,11 +116,15 @@ export class SickLeaveService {
|
|||
|
||||
return { success: true, data: update_pto };
|
||||
} catch (error) {
|
||||
return { success: false, error };
|
||||
console.error(error);
|
||||
return { success: false, error: ''};
|
||||
}
|
||||
};
|
||||
|
||||
takeSickLeaveHours = async (employee_id: number, asked_hours: number): Promise<Result<number, string>> => {
|
||||
takeSickLeaveHours = async (
|
||||
employee_id: number,
|
||||
asked_hours: number
|
||||
): Promise<Result<number, string>> => {
|
||||
if (asked_hours <= 0) return { success: false, error: 'INVALID_BANKING_HOURS' };
|
||||
|
||||
try {
|
||||
|
|
@ -154,74 +167,9 @@ export class SickLeaveService {
|
|||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { success: false, error: 'INVALID_BANKING_SHIFT' };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//LEAVE REQUEST FUNCTION - DEPRECATED
|
||||
// async calculateSickLeavePay(
|
||||
// employee_id: number,
|
||||
// reference_date: Date,
|
||||
// days_requested: number,
|
||||
// hours_per_day: number,
|
||||
// modifier: number,
|
||||
// ): Promise<Result<number, string>> {
|
||||
// if (days_requested <= 0 || hours_per_day <= 0 || modifier <= 0) {
|
||||
// return { success: true, data: 0 };
|
||||
// }
|
||||
|
||||
// //sets the year to jan 1st to dec 31st
|
||||
// const period_start = getYearStart(reference_date);
|
||||
// const period_end = reference_date;
|
||||
|
||||
// //fetches all shifts of a selected employee
|
||||
// const shifts = await this.prisma.shifts.findMany({
|
||||
// where: {
|
||||
// timesheet: { employee_id: employee_id },
|
||||
// date: { gte: period_start, lte: period_end },
|
||||
// },
|
||||
// select: { date: true },
|
||||
// });
|
||||
|
||||
// //count the amount of worked days
|
||||
// const worked_dates = new Set(
|
||||
// shifts.map((shift) => shift.date.toISOString().slice(0, 10)),
|
||||
// );
|
||||
// const days_worked = worked_dates.size;
|
||||
|
||||
// //less than 30 worked days returns 0
|
||||
// if (days_worked < 30) {
|
||||
// return { success: true, data: 0 };
|
||||
// }
|
||||
|
||||
// //default 3 days allowed after 30 worked days
|
||||
// let acquired_days = 3;
|
||||
|
||||
// //identify the date of the 30th worked day
|
||||
// const ordered_dates = Array.from(worked_dates).sort();
|
||||
// const threshold_date = new Date(ordered_dates[29]); // index 29 is the 30th day
|
||||
|
||||
// //calculate each completed month, starting the 1st of the next month
|
||||
// const first_bonus_date = new Date(
|
||||
// threshold_date.getFullYear(),
|
||||
// threshold_date.getMonth() + 1,
|
||||
// 1,
|
||||
// );
|
||||
// let months =
|
||||
// (period_end.getFullYear() - first_bonus_date.getFullYear()) * 12 +
|
||||
// (period_end.getMonth() - first_bonus_date.getMonth()) +
|
||||
// 1;
|
||||
// if (months < 0) months = 0;
|
||||
// acquired_days += months;
|
||||
|
||||
// //cap of 10 days
|
||||
// if (acquired_days > 10) acquired_days = 10;
|
||||
|
||||
// const payable_days = Math.min(acquired_days, days_requested);
|
||||
// const raw_hours = payable_days * hours_per_day * modifier;
|
||||
// const rounded = roundToQuarterHour(raw_hours);
|
||||
// return { success: true, data: rounded };
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export class VacationService {
|
|||
private readonly emailResolver: EmailToIdResolver,
|
||||
) { }
|
||||
|
||||
//switch employeeId for email
|
||||
async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise<Result<number, string>> {
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
if (!employee_id.success) return { success: false, error: employee_id.error }
|
||||
|
|
@ -20,7 +19,7 @@ export class VacationService {
|
|||
where: { id: employee_id.data },
|
||||
select: { first_work_day: true },
|
||||
});
|
||||
if (!employee) return { success: false, error: `Employee #${employee_id} not found` }
|
||||
if (!employee) return { success: false, error: `Employee #${employee_id.data} not found` }
|
||||
|
||||
const hire_date = employee.first_work_day;
|
||||
|
||||
|
|
@ -110,10 +109,8 @@ export class VacationService {
|
|||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { success: false, error: 'INVALID_VACATION_SHIFT' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Controller, Post, Param, Body, Patch, Delete, Req, UnauthorizedException, Query } from "@nestjs/common";
|
||||
import { Controller, Post, Param, Body, Patch, Delete, UnauthorizedException, Query } from "@nestjs/common";
|
||||
import { ExpenseDto } from "src/time-and-attendance/expenses/expense-create.dto";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
|
||||
|
|
@ -18,7 +18,10 @@ export class ExpenseController {
|
|||
|
||||
@Post('create')
|
||||
@ModuleAccessAllowed(ModulesEnum.timesheets)
|
||||
create(@Access('email') email: string, @Body() dto: ExpenseDto): Promise<Result<ExpenseDto, string>> {
|
||||
create(
|
||||
@Access('email') email: string,
|
||||
@Body() dto: ExpenseDto
|
||||
): Promise<Result<ExpenseDto, string>> {
|
||||
if (!email) throw new UnauthorizedException('Unauthorized User');
|
||||
return this.createService.createExpense(dto, email);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,6 @@ import { toDateFromString } from "src/common/utils/date-utils";
|
|||
import { ExpenseDto } from "src/time-and-attendance/expenses/expense-create.dto";
|
||||
import { NormalizedExpense } from "src/time-and-attendance/utils/type.utils";
|
||||
|
||||
//makes sure that a string cannot exceed 280 chars
|
||||
export const truncate280 = (input: string): string => {
|
||||
return input.length > 280 ? input.slice(0, 280) : input;
|
||||
}
|
||||
|
||||
//makes sure that the type of data of numeric values is valid
|
||||
export const parseOptionalNumber = (value: unknown, field: string) => {
|
||||
if (value == null) return undefined;
|
||||
const parsed = Number(value);
|
||||
if (Number.isNaN(parsed)) throw new Error(`Invalid value : ${value} for ${field}`);
|
||||
return parsed;
|
||||
};
|
||||
|
||||
//makes sure that comments are the right length the date is of Date type
|
||||
export const normalizeAndParseExpenseDto = async (dto: ExpenseDto): Promise<Result<NormalizedExpense, string>> => {
|
||||
const mileage = parseOptionalNumber(dto.mileage, "mileage");
|
||||
|
|
@ -38,3 +25,16 @@ export const normalizeAndParseExpenseDto = async (dto: ExpenseDto): Promise<Resu
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
//makes sure that a string cannot exceed 280 chars
|
||||
export const truncate280 = (input: string): string => {
|
||||
return input.length > 280 ? input.slice(0, 280) : input;
|
||||
}
|
||||
|
||||
//makes sure that the type of data of numeric values is valid
|
||||
export const parseOptionalNumber = (value: unknown, field: string) => {
|
||||
if (value == null) return undefined;
|
||||
const parsed = Number(value);
|
||||
if (Number.isNaN(parsed)) throw new Error(`Invalid value for ${field}`);
|
||||
return parsed;
|
||||
};
|
||||
|
|
@ -9,7 +9,9 @@ import { ExpenseCreateService } from "src/time-and-attendance/expenses/services/
|
|||
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
|
||||
|
||||
@Module({
|
||||
controllers: [ExpenseController],
|
||||
controllers: [
|
||||
ExpenseController
|
||||
],
|
||||
providers: [
|
||||
ExpenseCreateService,
|
||||
ExpenseUpdateService,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ export class ExpenseCreateService {
|
|||
private readonly payPeriodEventService: PayPeriodEventService,
|
||||
) { }
|
||||
|
||||
//_________________________________________________________________
|
||||
// CREATE
|
||||
//_________________________________________________________________
|
||||
async createExpense(dto: ExpenseDto, email: string): Promise<Result<ExpenseDto, string>> {
|
||||
async createExpense(
|
||||
dto: ExpenseDto,
|
||||
email: string
|
||||
): Promise<Result<ExpenseDto, string>> {
|
||||
try {
|
||||
//fetch employee_id using req.user.email
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
|
|
@ -75,6 +75,7 @@ export class ExpenseCreateService {
|
|||
|
||||
return { success: true, data: created };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { success: false, error: 'INVALID_EXPENSE' };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ export class ExpenseDeleteService {
|
|||
private readonly prisma: PrismaPostgresService,
|
||||
private readonly payPeriodEventService: PayPeriodEventService,
|
||||
private readonly emailResolver: EmailToIdResolver,
|
||||
){}
|
||||
) { }
|
||||
|
||||
//_________________________________________________________________
|
||||
// DELETE
|
||||
//_________________________________________________________________
|
||||
async deleteExpense(expense_id: number, email: string): Promise<Result<number, string>> {
|
||||
async deleteExpense(
|
||||
expense_id: number,
|
||||
email: string
|
||||
): Promise<Result<number, string>> {
|
||||
// get employee id of employee who made delete request
|
||||
const employee = await this.emailResolver.findIdByEmail(email);
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ export class ExpenseDeleteService {
|
|||
|
||||
// confirm ownership of expense to employee who made request
|
||||
const expense = await this.prisma.expenses.findUnique({
|
||||
where: { id: expense_id},
|
||||
where: { id: expense_id },
|
||||
select: {
|
||||
timesheet: {
|
||||
select: {
|
||||
|
|
@ -33,7 +33,7 @@ export class ExpenseDeleteService {
|
|||
}
|
||||
});
|
||||
|
||||
if (!expense || expense.timesheet.employee_id !== employee.data) return { success: false, error: 'EXPENSE_NOT_FOUND'};
|
||||
if (!expense || expense.timesheet.employee_id !== employee.data) return { success: false, error: 'EXPENSE_NOT_FOUND' };
|
||||
|
||||
try {
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
|
|
@ -56,6 +56,7 @@ export class ExpenseDeleteService {
|
|||
|
||||
return { success: true, data: expense_id };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { success: false, error: `EXPENSE_NOT_FOUND` };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ export class ExpenseUpdateService {
|
|||
private readonly typeResolver: BankCodesResolver,
|
||||
private readonly payPeriodEventService: PayPeriodEventService,
|
||||
) { }
|
||||
//_________________________________________________________________
|
||||
// UPDATE
|
||||
//_________________________________________________________________
|
||||
async updateExpense(dto: ExpenseDto, email: string, employee_email?: string): Promise<Result<ExpenseDto, string>> {
|
||||
|
||||
async updateExpense(
|
||||
dto: ExpenseDto,
|
||||
email: string,
|
||||
employee_email?: string
|
||||
): Promise<Result<ExpenseDto, string>> {
|
||||
try {
|
||||
const account_email = employee_email ?? email;
|
||||
//fetch employee_id using req.user.email
|
||||
|
|
@ -79,6 +81,7 @@ export class ExpenseUpdateService {
|
|||
|
||||
return { success: true, data: updated };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { success: false, error: 'EXPENSE_NOT_FOUND' };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class CsvExportController {
|
|||
},
|
||||
}
|
||||
);
|
||||
const csv_buffer = await this.generator.generateCsv(rows);
|
||||
const csv_buffer = this.generator.generateCsv(rows);
|
||||
|
||||
response.set({
|
||||
'Content-Type': 'text/csv; charset=utf-8',
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
|||
HolidayService,
|
||||
EmailToIdResolver,
|
||||
],
|
||||
controllers: [CsvExportController],
|
||||
controllers: [
|
||||
CsvExportController
|
||||
],
|
||||
})
|
||||
export class CsvExportModule { }
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const consolidateRowHoursAndAmountByType = (rows: InternalCsvRow[]): Inte
|
|||
const map = new Map<string, InternalCsvRow>();
|
||||
|
||||
for (const row of rows) {
|
||||
if (row.code = VACATION) {
|
||||
if (row.code === VACATION) {
|
||||
map.set(`${row.code}|${row.shift_date}`, row);
|
||||
} else {
|
||||
const key = `${row.code}|${row.semaine_no}`;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import { CsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
|
|||
@Injectable()
|
||||
export class CsvGeneratorService {
|
||||
//csv builder and "mise en page"
|
||||
generateCsv(rows: CsvRow[]): Buffer {
|
||||
generateCsv(
|
||||
rows: CsvRow[]
|
||||
): Buffer {
|
||||
const body = rows.map(row => {
|
||||
const quantity_hours = (typeof row.quantite_hre === 'number') ? row.quantite_hre.toFixed(2) : '';
|
||||
const amount = (typeof row.montant === 'number') ? row.montant.toFixed(2) : '';
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ export class CsvExportService {
|
|||
* @returns The desired filtered data in semi-colon-separated format, grouped and sorted by
|
||||
* employee and by bank codes.
|
||||
*/
|
||||
async collectTransaction(year: number, period_no: number, filters: Filters): Promise<CsvRow[]> {
|
||||
async collectTransaction(
|
||||
year: number,
|
||||
period_no: number,
|
||||
filters: Filters
|
||||
): Promise<CsvRow[]> {
|
||||
const BILLABLE_SHIFT_TYPES: BillableShiftType[] = [];
|
||||
|
||||
if (filters.types.shifts) BILLABLE_SHIFT_TYPES.push('REGULAR', 'OVERTIME', 'EMERGENCY', 'EVENING', 'SICK');
|
||||
|
|
@ -70,10 +74,10 @@ export class CsvExportService {
|
|||
});
|
||||
|
||||
const rows: InternalCsvRow[] = exportedShifts.map(shift => {
|
||||
const employee = shift!.timesheet.employee;
|
||||
const week = computeWeekNumber(start, shift!.date);
|
||||
const type_transaction = shift!.bank_code.bank_code.charAt(0);
|
||||
const code = Number(shift!.bank_code.bank_code.slice(1,));
|
||||
const employee = shift.timesheet.employee;
|
||||
const week = computeWeekNumber(start, shift.date);
|
||||
const type_transaction = shift.bank_code.bank_code.charAt(0);
|
||||
const code = Number(shift.bank_code.bank_code.slice(1,));
|
||||
const isPTO = PTO_SHIFT_CODES.includes(shift.bank_code.bank_code)
|
||||
|
||||
return {
|
||||
|
|
@ -84,7 +88,7 @@ export class CsvExportService {
|
|||
releve: 0,
|
||||
type_transaction: type_transaction,
|
||||
code: code,
|
||||
quantite_hre: computeHours(shift!.start_time, shift!.end_time),
|
||||
quantite_hre: computeHours(shift.start_time, shift.end_time),
|
||||
taux_horaire: '',
|
||||
montant: undefined,
|
||||
semaine_no: week,
|
||||
|
|
@ -93,8 +97,8 @@ export class CsvExportService {
|
|||
departem_no: undefined,
|
||||
sous_departem_no: undefined,
|
||||
date_transaction: formatDate(end),
|
||||
premier_jour_absence: isPTO ? formatDate(shift!.date) : '',
|
||||
dernier_jour_absence: isPTO ? formatDate(shift!.date) : '',
|
||||
premier_jour_absence: isPTO ? formatDate(shift.date) : '',
|
||||
dernier_jour_absence: isPTO ? formatDate(shift.date) : '',
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -110,8 +114,8 @@ export class CsvExportService {
|
|||
|
||||
exportedExpenses.map(expense => {
|
||||
const employee = expense.timesheet.employee;
|
||||
const type_transaction = expense!.bank_code.bank_code.charAt(0);
|
||||
const code = Number(expense!.bank_code.bank_code.slice(1,))
|
||||
const type_transaction = expense.bank_code.bank_code.charAt(0);
|
||||
const code = Number(expense.bank_code.bank_code.slice(1,))
|
||||
const week = computeWeekNumber(start, expense.date);
|
||||
|
||||
rows.push({
|
||||
|
|
@ -147,7 +151,7 @@ export class CsvExportService {
|
|||
});
|
||||
|
||||
const holiday_rows = await applyHolidayRequalifications(rows, this.holiday_service, HOLIDAY_SHIFT_CODE[0]);
|
||||
const consolidated_rows = await consolidateRowHoursAndAmountByType(holiday_rows);
|
||||
const consolidated_rows = consolidateRowHoursAndAmountByType(holiday_rows);
|
||||
//requalifies regular hours into overtime when needed
|
||||
const requalified_rows = await applyOvertimeRequalifications(consolidated_rows, this.overtime_service);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ export type NormalizedExpense = {
|
|||
amount?: number | Prisma.Decimal | null;
|
||||
mileage?: number | Prisma.Decimal | null;
|
||||
attachment?: number;
|
||||
// bank_code_id: number;
|
||||
};
|
||||
|
||||
export type NormalizedLeaveRequest = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user