refactor(EsLint): EsLint corrections
This commit is contained in:
parent
37a4da7923
commit
aa72651a67
|
|
@ -1,6 +1,6 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
import eslint from '@eslint/js';
|
import eslint from '@eslint/js';
|
||||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
// import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
import tseslint from 'typescript-eslint';
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ export default tseslint.config(
|
||||||
},
|
},
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
eslintPluginPrettierRecommended,
|
// eslintPluginPrettierRecommended,
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller } from '@nestjs/common';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController { }
|
export class AppController { }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { BadRequestException, Module, ValidationPipe } from '@nestjs/common';
|
import { BadRequestException, Module, ValidationPipe } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { NotificationsModule } from './shared/notifications/notifications.module';
|
|
||||||
import { PrismaPostgresModule } from '../prisma/postgres/prisma-postgres.module';
|
import { PrismaPostgresModule } from '../prisma/postgres/prisma-postgres.module';
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
@ -21,7 +20,6 @@ import { CustomerSupportModule } from 'src/customer-support/customer-support.mod
|
||||||
AuthenticationModule,
|
AuthenticationModule,
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
ScheduleModule.forRoot(), //cronjobs
|
ScheduleModule.forRoot(), //cronjobs
|
||||||
NotificationsModule,
|
|
||||||
PrismaPostgresModule,
|
PrismaPostgresModule,
|
||||||
PrismaMariadbModule,
|
PrismaMariadbModule,
|
||||||
PrismaLegacyModule,
|
PrismaLegacyModule,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ export class ChatbotController {
|
||||||
|
|
||||||
@Post('')
|
@Post('')
|
||||||
@ModuleAccessAllowed(ModulesEnum.chatbot)
|
@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);
|
return await this.chatbotService.pingExternalApi(body, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,14 @@ import { HttpModule } from '@nestjs/axios';
|
||||||
import { ChatbotService } from 'src/chatbot/chatbot.service';
|
import { ChatbotService } from 'src/chatbot/chatbot.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [HttpModule],
|
imports: [
|
||||||
controllers: [ChatbotController],
|
HttpModule,
|
||||||
providers: [ChatbotService],
|
],
|
||||||
exports: [],
|
controllers: [
|
||||||
|
ChatbotController,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ChatbotService,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ChatbotModule { }
|
export class ChatbotModule { }
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UserMessageDto } from 'src/chatbot/dtos/user-message.dto';
|
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { HttpService } from '@nestjs/axios';
|
||||||
import { firstValueFrom } from 'rxjs';
|
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';
|
import { Message } from 'src/chatbot/dtos/dialog-message.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -14,41 +13,11 @@ export class ChatbotService {
|
||||||
const { data } = await firstValueFrom(this.httpService.post(
|
const { data } = await firstValueFrom(this.httpService.post(
|
||||||
'https://n8nai.targo.ca/webhook/chatty-Mcbot',
|
'https://n8nai.targo.ca/webhook/chatty-Mcbot',
|
||||||
{ userInput: body.userInput, userId: email, sessionId: this.sessionId, pageContext: body.pageContext ?? undefined }
|
{ userInput: body.userInput, userId: email, sessionId: this.sessionId, pageContext: body.pageContext ?? undefined }
|
||||||
));
|
)) as ChatbotResponseDto;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: data[0].output,
|
text: data[0].output,
|
||||||
sent: false,
|
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';
|
import { IsBoolean, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class Message {
|
export class Message {
|
||||||
@IsString()
|
@IsString() text: string;
|
||||||
text!: string;
|
@IsBoolean() sent: boolean;
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
sent!: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
import { IsArray, IsString } from 'class-validator';
|
import { IsArray, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class PageContextDto {
|
export class PageContextDto {
|
||||||
@IsString()
|
@IsString() name: string;
|
||||||
name: string;
|
@IsString() description: string;
|
||||||
|
@IsArray() features: string[];
|
||||||
@IsString()
|
@IsString() @IsOptional() path?: string;
|
||||||
description: string;
|
|
||||||
|
|
||||||
@IsArray()
|
|
||||||
features: string[];
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
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 { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
import { PageContextDto } from './page-context.dto';
|
import { PageContextDto } from './page-context.dto';
|
||||||
|
|
||||||
export class UserMessageDto {
|
export class UserMessageDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@Transform(({ value }) => value.trim())
|
@IsString() userInput: string;
|
||||||
userInput!: string;
|
|
||||||
@IsOptional() @Type(() => PageContextDto) pageContext?: PageContextDto | undefined;
|
@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 { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
||||||
|
import { UserDto } from "src/identity-and-account/users-management/user.dto";
|
||||||
|
|
||||||
export const Access = createParamDecorator(
|
export interface AuthenticatedRequest extends Request {
|
||||||
(data:string, ctx: ExecutionContext) => {
|
user: UserDto;
|
||||||
const request = ctx.switchToHttp().getRequest();
|
}
|
||||||
const user = request.user;
|
|
||||||
|
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;
|
return data ? user?.[data] : user;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -7,13 +7,7 @@ import {
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { MODULES_KEY } from '../decorators/modules-guard.decorators';
|
import { MODULES_KEY } from '../decorators/modules-guard.decorators';
|
||||||
import { Modules } from "prisma/postgres/generated/prisma/client/postgres/client";
|
import { Modules } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||||
|
import { AuthenticatedRequest } from 'src/common/decorators/module-access.decorators';
|
||||||
|
|
||||||
|
|
||||||
interface RequestWithUser extends Request {
|
|
||||||
// TODO: Create an actual user model based on OAuth signin
|
|
||||||
user: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ModulesGuard implements CanActivate {
|
export class ModulesGuard implements CanActivate {
|
||||||
|
|
@ -27,7 +21,7 @@ export class ModulesGuard implements CanActivate {
|
||||||
if (!requiredModules || requiredModules.length === 0) {
|
if (!requiredModules || requiredModules.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const request = ctx.switchToHttp().getRequest<RequestWithUser>();
|
const request = ctx.switchToHttp().getRequest<AuthenticatedRequest>();
|
||||||
const user = request.user;
|
const user = request.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
@ -36,7 +30,7 @@ export class ModulesGuard implements CanActivate {
|
||||||
for (const module of requiredModules) {
|
for (const module of requiredModules) {
|
||||||
if (!user.user_module_access.includes(module)) {
|
if (!user.user_module_access.includes(module)) {
|
||||||
throw new ForbiddenException(
|
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 { Injectable } from "@nestjs/common";
|
||||||
import { Prisma, PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/client";
|
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 { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||||
|
|
||||||
|
|
@ -25,7 +25,9 @@ export class BankCodesResolver {
|
||||||
};
|
};
|
||||||
|
|
||||||
//finds only id by type
|
//finds only id by type
|
||||||
readonly findBankCodeIDByType = async (type: string, client?: Tx
|
readonly findBankCodeIDByType = async (
|
||||||
|
type: string,
|
||||||
|
client?: Tx
|
||||||
): Promise<Result<number, string>> => {
|
): Promise<Result<number, string>> => {
|
||||||
const db = (client ?? this.prisma) as PrismaClient;
|
const db = (client ?? this.prisma) as PrismaClient;
|
||||||
const bank_code = await db.bankCodes.findFirst({
|
const bank_code = await db.bankCodes.findFirst({
|
||||||
|
|
@ -37,7 +39,9 @@ export class BankCodesResolver {
|
||||||
return { success: true, data: bank_code.id };
|
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>> => {
|
): Promise<Result<string, string>> => {
|
||||||
const db = (client ?? this.prisma) as PrismaClient;
|
const db = (client ?? this.prisma) as PrismaClient;
|
||||||
const bank_code = await db.bankCodes.findFirst({
|
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 { 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 { Prisma, PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||||
|
|
||||||
|
|
@ -12,7 +12,9 @@ export class EmailToIdResolver {
|
||||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||||
|
|
||||||
// find employee_id using email
|
// find employee_id using email
|
||||||
readonly findIdByEmail = async (email: string, client?: Tx
|
readonly findIdByEmail = async (
|
||||||
|
email: string,
|
||||||
|
client?: Tx
|
||||||
): Promise<Result<number, string>> => {
|
): Promise<Result<number, string>> => {
|
||||||
const db = (client ?? this.prisma) as PrismaClient;
|
const db = (client ?? this.prisma) as PrismaClient;
|
||||||
const employee = await db.employees.findFirst({
|
const employee = await db.employees.findFirst({
|
||||||
|
|
@ -24,7 +26,9 @@ export class EmailToIdResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
// find user_id using email
|
// find user_id using email
|
||||||
readonly resolveUserIdWithEmail = async (email: string, client?: Tx
|
readonly resolveUserIdWithEmail = async (
|
||||||
|
email: string,
|
||||||
|
client?: Tx
|
||||||
): Promise<Result<string, string>> => {
|
): Promise<Result<string, string>> => {
|
||||||
const db = (client ?? this.prisma) as PrismaClient;
|
const db = (client ?? this.prisma) as PrismaClient;
|
||||||
const user = await db.users.findFirst({
|
const user = await db.users.findFirst({
|
||||||
|
|
@ -34,6 +38,4 @@ export class EmailToIdResolver {
|
||||||
if (!user) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
|
if (!user) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
|
||||||
return { success: true, data: user.id };
|
return { success: true, data: user.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly findFullNameByEmail
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
|
||||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||||
import { Prisma, PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/client";
|
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;
|
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||||
|
|
||||||
|
|
@ -9,7 +9,10 @@ type Tx = Prisma.TransactionClient | PrismaClient;
|
||||||
export class FullNameResolver {
|
export class FullNameResolver {
|
||||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
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 db = (client ?? this.prisma) as PrismaClient;
|
||||||
const employee = await db.employees.findUnique({
|
const employee = await db.employees.findUnique({
|
||||||
where: { id: employee_id },
|
where: { id: employee_id },
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ interface ShiftKey {
|
||||||
export class ShiftIdResolver {
|
export class ShiftIdResolver {
|
||||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||||
|
|
||||||
readonly findShiftIdByData = async (key: ShiftKey, client?: Tx
|
readonly findShiftIdByData = async (
|
||||||
|
key: ShiftKey,
|
||||||
|
client?: Tx
|
||||||
): Promise<Result<number, string>> => {
|
): Promise<Result<number, string>> => {
|
||||||
const db = (client ?? this.prisma) as PrismaClient;
|
const db = (client ?? this.prisma) as PrismaClient;
|
||||||
const shift = await db.shifts.findFirst({
|
const shift = await db.shifts.findFirst({
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
|
||||||
import { EmailToIdResolver } from "./email-id.mapper";
|
import { EmailToIdResolver } from "./email-id.mapper";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
|
||||||
import { weekStartSunday } from "src/common/utils/date-utils";
|
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 { PrismaClient } from "prisma/postgres/generated/prisma/client/postgres/internal/class";
|
||||||
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
|
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||||
|
|
||||||
|
|
@ -16,7 +16,11 @@ export class EmployeeTimesheetResolver {
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
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 db = (client ?? this.prisma) as PrismaClient;
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
if (!employee_id.success) return { success: false, error: employee_id.error }
|
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>;
|
protected abstract delegateFor(tx: TransactionClient): UpdatableDelegate<T>;
|
||||||
|
|
||||||
//standard update Aproval
|
//standard update Aproval
|
||||||
async updateApproval(id: number, is_approved: boolean): Promise<T> {
|
async updateApproval(
|
||||||
|
id: number,
|
||||||
|
is_approved: boolean
|
||||||
|
): Promise<T> {
|
||||||
try {
|
try {
|
||||||
return await this.delegate.update({
|
return await this.delegate.update({
|
||||||
where: { id },
|
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 {
|
try {
|
||||||
return await this.delegateFor(tx).update({
|
return await this.delegateFor(tx).update({
|
||||||
where: { id },
|
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({
|
@Module({
|
||||||
imports: [ PassportModule.register({
|
imports: [
|
||||||
|
PassportModule.register({
|
||||||
session: true,
|
session: true,
|
||||||
defaultStrategy: 'openidconnect'
|
defaultStrategy: 'openidconnect'
|
||||||
}), UsersModule, ],
|
}), UsersModule,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AuthentikAuthService,
|
AuthentikAuthService,
|
||||||
AuthentikStrategy,
|
AuthentikStrategy,
|
||||||
ExpressSessionSerializer,
|
ExpressSessionSerializer,
|
||||||
UsersService,
|
UsersService,
|
||||||
],
|
],
|
||||||
exports: [ AuthentikAuthService ],
|
exports: [
|
||||||
controllers: [AuthController],
|
AuthentikAuthService
|
||||||
|
],
|
||||||
|
controllers: [
|
||||||
|
AuthController
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AuthenticationModule { }
|
export class AuthenticationModule { }
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export class AuthController {
|
||||||
|
|
||||||
@Get('callback')
|
@Get('callback')
|
||||||
@UseGuards(OIDCLoginGuard)
|
@UseGuards(OIDCLoginGuard)
|
||||||
loginCallback(@Req() req: Request, @Res() res: Response) {
|
loginCallback(@Req() _req: Request, @Res() res: Response) {
|
||||||
res.redirect(process.env.REDIRECT_URL_DEV!);
|
res.redirect(process.env.REDIRECT_URL_DEV!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { Request } from 'express';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OIDCLoginGuard extends AuthGuard('openidconnect') {
|
export class OIDCLoginGuard extends AuthGuard('openidconnect') {
|
||||||
async canActivate(context: ExecutionContext) {
|
async canActivate(context: ExecutionContext) {
|
||||||
const result = (await super.canActivate(context)) as boolean;
|
const result = (await super.canActivate(context)) as boolean;
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
await super.logIn(request);
|
await super.logIn(request);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export class ExpressSessionSerializer extends PassportSerializer {
|
||||||
}
|
}
|
||||||
done(null, user);
|
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){
|
if (!payload){
|
||||||
done(new UnauthorizedException('Deserialize user error'), payload);
|
done(new UnauthorizedException('Deserialize user error'), payload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UsersService } from 'src/identity-and-account/users-management/services/users.service';
|
import { UsersService } from 'src/identity-and-account/users-management/services/users.service';
|
||||||
|
import { UserDto } from 'src/identity-and-account/users-management/user.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthentikAuthService {
|
export class AuthentikAuthService {
|
||||||
constructor(private usersService: UsersService) {}
|
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);
|
const user = await this.usersService.findOneByEmail(user_email);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidcon
|
||||||
|
|
||||||
async validate(
|
async validate(
|
||||||
_issuer: string,
|
_issuer: string,
|
||||||
profile: Profile,
|
_profile: Profile,
|
||||||
_context: any,
|
_context: any,
|
||||||
_idToken: string,
|
idToken: string,
|
||||||
_accessToken: string,
|
_accessToken: string,
|
||||||
_refreshToken: string,
|
_refreshToken: string,
|
||||||
_params: any,
|
_params: any,
|
||||||
|
|
@ -50,9 +50,9 @@ export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidcon
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
||||||
const components = _idToken.split('.');
|
const components = idToken.split('.');
|
||||||
const payload = Buffer.from(components[1], "base64").toString('utf-8');
|
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);
|
if (!claims.email) return cb(new Error('Missing email in OIDC profile'), false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,17 @@ export class EmployeesController {
|
||||||
|
|
||||||
@Get('personal-profile')
|
@Get('personal-profile')
|
||||||
@ModuleAccessAllowed(ModulesEnum.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);
|
return await this.getService.findOwnProfile(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('profile')
|
@Get('profile')
|
||||||
@ModuleAccessAllowed(ModulesEnum.personal_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>> {
|
): Promise<Result<Partial<EmployeeDetailedDto>, string>> {
|
||||||
return await this.getService.findOneDetailedProfile(email, employee_email);
|
return await this.getService.findOneDetailedProfile(email, employee_email);
|
||||||
}
|
}
|
||||||
|
|
@ -37,13 +41,17 @@ export class EmployeesController {
|
||||||
|
|
||||||
@Post('create')
|
@Post('create')
|
||||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
@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);
|
return await this.createService.createEmployee(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch('update')
|
@Patch('update')
|
||||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||||
async updateEmployee(@Body() dto:EmployeeDetailedUpsertDto){
|
async updateEmployee(
|
||||||
|
@Body() dto:EmployeeDetailedUpsertDto
|
||||||
|
){
|
||||||
return await this.updateService.updateEmployee(dto);
|
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';
|
import { EmployeesCreateService } from 'src/identity-and-account/employees/services/employees-create.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
controllers: [
|
||||||
controllers: [EmployeesController],
|
EmployeesController
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
EmployeesGetService,
|
EmployeesGetService,
|
||||||
EmployeesUpdateService,
|
EmployeesUpdateService,
|
||||||
|
|
@ -16,6 +17,8 @@ import { EmployeesCreateService } from 'src/identity-and-account/employees/servi
|
||||||
AccessGetService,
|
AccessGetService,
|
||||||
EmailToIdResolver
|
EmailToIdResolver
|
||||||
],
|
],
|
||||||
exports: [EmployeesGetService],
|
exports: [
|
||||||
|
EmployeesGetService
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class EmployeesModule { }
|
export class EmployeesModule { }
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ export class HomePageController {
|
||||||
|
|
||||||
@Get('help')
|
@Get('help')
|
||||||
@ModuleAccessAllowed(ModulesEnum.dashboard)
|
@ModuleAccessAllowed(ModulesEnum.dashboard)
|
||||||
async getIntroductionHelper(@Access('email') email: string) {
|
async getIntroductionHelper(
|
||||||
|
@Access('email') email: string
|
||||||
|
) {
|
||||||
return await this.homePageService.buildHomePageHelpMessage(email);
|
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";
|
import { HomePageService } from "src/identity-and-account/help/help-page.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [HomePageController],
|
controllers: [
|
||||||
providers: [HomePageService, EmailToIdResolver],
|
HomePageController
|
||||||
exports: [HomePageService],
|
],
|
||||||
|
providers: [
|
||||||
|
HomePageService,
|
||||||
|
EmailToIdResolver
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
HomePageService
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class HomePageModule { };
|
export class HomePageModule { };
|
||||||
|
|
@ -10,7 +10,9 @@ export class HomePageService {
|
||||||
private readonly emailresolver: EmailToIdResolver,
|
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);
|
const user_id = await this.emailresolver.resolveUserIdWithEmail(email);
|
||||||
if (!user_id.success) return { success: false, error: 'INVALID_EMAIL' };
|
if (!user_id.success) return { success: false, error: 'INVALID_EMAIL' };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,19 @@ export class PreferencesController {
|
||||||
|
|
||||||
@Patch('update')
|
@Patch('update')
|
||||||
@ModuleAccessAllowed(ModulesEnum.personal_profile)
|
@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>> {
|
): Promise<Result<PreferencesDto, string>> {
|
||||||
return this.service.updatePreferences(email, payload);
|
return this.service.updatePreferences(email, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ModuleAccessAllowed(ModulesEnum.personal_profile)
|
@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);
|
return this.service.findPreferences(email, employee_email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,16 @@ import { PreferencesService } from "./preferences.service";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [ PreferencesController ],
|
controllers: [
|
||||||
providers: [ PreferencesService, EmailToIdResolver ],
|
PreferencesController
|
||||||
exports: [ PreferencesService ],
|
],
|
||||||
|
providers: [
|
||||||
|
PreferencesService,
|
||||||
|
EmailToIdResolver
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
PreferencesService
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
export class PreferencesModule {}
|
export class PreferencesModule {}
|
||||||
|
|
@ -11,7 +11,10 @@ export class PreferencesService {
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
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 account_email = employee_email ?? email;
|
||||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
@ -42,7 +45,10 @@ export class PreferencesService {
|
||||||
return { success: true, data: preferences };
|
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);
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
|
||||||
if (!user_id.success) return { success: false, error: user_id.error }
|
if (!user_id.success) return { success: false, error: user_id.error }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { IsBoolean } from "class-validator";
|
import { IsBoolean } from "class-validator";
|
||||||
|
|
||||||
export class ModuleAccess {
|
export class ModuleAccess {
|
||||||
@IsBoolean() timesheets!: boolean;
|
@IsBoolean() timesheets: boolean;
|
||||||
@IsBoolean() timesheets_approval!: boolean;
|
@IsBoolean() timesheets_approval: boolean;
|
||||||
@IsBoolean() employee_list!: boolean;
|
@IsBoolean() employee_list: boolean;
|
||||||
@IsBoolean() employee_management!: boolean;
|
@IsBoolean() employee_management: boolean;
|
||||||
@IsBoolean() personal_profile!: boolean;
|
@IsBoolean() personal_profile: boolean;
|
||||||
@IsBoolean() dashboard!: 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 { Access } from "src/common/decorators/module-access.decorators";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { ModuleAccess } from "src/identity-and-account/user-module-access/module-acces.dto";
|
import { ModuleAccess } from "src/identity-and-account/user-module-access/module-acces.dto";
|
||||||
|
|
@ -16,7 +16,9 @@ export class ModuleAccessController {
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
@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>> {
|
): Promise<Result<boolean, string>> {
|
||||||
await this.getService.findModuleAccess(email, employee_email);
|
await this.getService.findModuleAccess(email, employee_email);
|
||||||
return { success: true, data: true };
|
return { success: true, data: true };
|
||||||
|
|
@ -24,7 +26,10 @@ export class ModuleAccessController {
|
||||||
|
|
||||||
@Patch('update')
|
@Patch('update')
|
||||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
@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>> {
|
): Promise<Result<boolean, string>> {
|
||||||
await this.updateService.updateModuleAccess(email, dto, employee_email);
|
await this.updateService.updateModuleAccess(email, dto, employee_email);
|
||||||
return { success: true, data: true };
|
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";
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [ModuleAccessController],
|
controllers: [
|
||||||
providers: [AccessUpdateService, AccessGetService, EmailToIdResolver],
|
ModuleAccessController
|
||||||
exports: [AccessGetService],
|
],
|
||||||
|
providers: [
|
||||||
|
AccessUpdateService,
|
||||||
|
AccessGetService,
|
||||||
|
EmailToIdResolver
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AccessGetService
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ModuleAccessModule { }
|
export class ModuleAccessModule { }
|
||||||
|
|
@ -12,7 +12,10 @@ export class AccessGetService {
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
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 account_email = employee_email ?? email;
|
||||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ export class AccessUpdateService {
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
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 account_email = employee_email ?? email;
|
||||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
@ -52,7 +56,10 @@ export class AccessUpdateService {
|
||||||
return { success: true, data: updated_access };
|
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 account_email = employee_email ?? email;
|
||||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
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 {
|
export abstract class AbstractUserService {
|
||||||
constructor(protected readonly prisma: PrismaPostgresService) { }
|
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({
|
const user = await this.prisma.users.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
include: {
|
include: {
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@ export class UserDto {
|
||||||
@IsString() last_name: string;
|
@IsString() last_name: string;
|
||||||
@IsEmail() email: string;
|
@IsEmail() email: string;
|
||||||
@IsEnum(Roles) role: 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';
|
import { PrismaPostgresModule } from 'prisma/postgres/prisma-postgres.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaPostgresModule],
|
imports: [
|
||||||
providers: [UsersService],
|
PrismaPostgresModule
|
||||||
exports: [UsersService],
|
],
|
||||||
|
providers: [
|
||||||
|
UsersService
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
UsersService
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
|
|
||||||
10
src/main.ts
10
src/main.ts
|
|
@ -1,8 +1,8 @@
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import * as nodeCrypto from 'crypto';
|
// import * as nodeCrypto from 'crypto';
|
||||||
if (!(globalThis as any).crypto) {
|
// if (!(globalThis as any).crypto) {
|
||||||
(globalThis as any).crypto = nodeCrypto;
|
// (globalThis as any).crypto = nodeCrypto;
|
||||||
}
|
// }
|
||||||
import { NestFactory, Reflector } from '@nestjs/core';
|
import { NestFactory, Reflector } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { ModulesGuard } from './common/guards/modules.guard';
|
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
|
const SESSION_TOKEN_DURATION_MINUTES = 180
|
||||||
|
|
||||||
async function bootstrap() {
|
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 app = await NestFactory.create(AppModule);
|
||||||
const prisma_postgres = app.get(PrismaPostgresService);
|
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)
|
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||||
export class BankCodesControllers {
|
export class BankCodesControllers {
|
||||||
constructor(private readonly bankCodesService: BankCodesService) { }
|
constructor(private readonly bankCodesService: BankCodesService) { }
|
||||||
//_____________________________________________________________________________________________
|
|
||||||
// Deprecated or unused methods
|
|
||||||
//_____________________________________________________________________________________________
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
create(@Body() dto: Prisma.BankCodesCreateInput
|
create(
|
||||||
|
@Body() dto: Prisma.BankCodesCreateInput
|
||||||
): Promise<Result<boolean, string>> {
|
): Promise<Result<boolean, string>> {
|
||||||
return this.bankCodesService.create(dto);
|
return this.bankCodesService.create(dto);
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +22,9 @@ export class BankCodesControllers {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@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>> {
|
): Promise<Result<boolean, string>> {
|
||||||
return this.bankCodesService.update(id, dto)
|
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";
|
import { BankCodesService } from "src/time-and-attendance/bank-codes/bank-codes.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [BankCodesControllers],
|
controllers: [
|
||||||
providers: [BankCodesService, PrismaPostgresService],
|
BankCodesControllers
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
BankCodesService,
|
||||||
|
PrismaPostgresService
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
export class BankCodesModule {}
|
export class BankCodesModule {}
|
||||||
|
|
@ -7,7 +7,9 @@ import { Result } from "src/common/errors/result-error.factory";
|
||||||
export class BankCodesService {
|
export class BankCodesService {
|
||||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||||
|
|
||||||
async create(dto: Prisma.BankCodesCreateInput): Promise<Result<boolean, string>> {
|
async create(
|
||||||
|
dto: Prisma.BankCodesCreateInput
|
||||||
|
): Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
await this.prisma.bankCodes.create({
|
await this.prisma.bankCodes.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -27,7 +29,10 @@ export class BankCodesService {
|
||||||
return this.prisma.bankCodes.findMany();
|
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 {
|
try {
|
||||||
await this.prisma.bankCodes.update({
|
await this.prisma.bankCodes.update({
|
||||||
where: { id },
|
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 {
|
try {
|
||||||
await this.prisma.bankCodes.delete({
|
await this.prisma.bankCodes.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports:[],
|
|
||||||
providers: [
|
providers: [
|
||||||
HolidayService,
|
HolidayService,
|
||||||
MileageService,
|
MileageService,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ export class BankedHoursService {
|
||||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
constructor(private readonly prisma: PrismaPostgresService) { }
|
||||||
|
|
||||||
//manage shifts with bank_code.type BANKING
|
//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' };
|
if (asked_hours <= 0) return { success: false, error: 'INVALID_BANKING_HOURS' };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -50,8 +54,6 @@ export class BankedHoursService {
|
||||||
} else if (type === 'WITHDRAW_BANKED') {
|
} else if (type === 'WITHDRAW_BANKED') {
|
||||||
if (asked_hours > banked_hours) {
|
if (asked_hours > banked_hours) {
|
||||||
return { success: true, data: banked_hours } as Result<number, string>;
|
return { success: true, data: banked_hours } as Result<number, string>;
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
await tx.paidTimeOff.update({
|
await tx.paidTimeOff.update({
|
||||||
where: { employee_id: employee.id },
|
where: { employee_id: employee.id },
|
||||||
|
|
@ -69,7 +71,8 @@ export class BankedHoursService {
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
} catch (error) {
|
} 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`
|
* @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 {
|
try {
|
||||||
const valid_codes = ['G1', 'G43', 'G56', 'G104', 'G105', 'G305', 'G700', 'G720'];
|
const valid_codes = ['G1', 'G43', 'G56', 'G104', 'G105', 'G305', 'G700', 'G720'];
|
||||||
const holiday_week_start = getWeekStart(holiday_date);
|
const holiday_week_start = getWeekStart(holiday_date);
|
||||||
|
|
@ -76,11 +80,16 @@ export class HolidayService {
|
||||||
const average_daily_hours = capped_total / 20;
|
const average_daily_hours = capped_total / 20;
|
||||||
return { success: true, data: average_daily_hours };
|
return { success: true, data: average_daily_hours };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return { success: false, error: `an error occureded during holiday calculation` }
|
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);
|
const average_daily_hours = await this.computeHoursPrevious4Weeks(employeePayrollID, companyCode, holiday_date);
|
||||||
|
|
||||||
if (!average_daily_hours.success) return { success: false, error: average_daily_hours.error };
|
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) { }
|
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' };
|
if (amount < 0) return { success: false, error: 'The amount must be higher than 0' };
|
||||||
|
|
||||||
//fetch modifier
|
//fetch modifier
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,21 @@ type WeekOvertimeSummary = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OvertimeService {
|
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) { }
|
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 db = (tx ?? this.prisma) as PrismaClient;
|
||||||
|
|
||||||
const week_start = getWeekStart(date);
|
const week_start = getWeekStart(date);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ import { Result } from "src/common/errors/result-error.factory";
|
||||||
export class SickLeaveService {
|
export class SickLeaveService {
|
||||||
constructor(private readonly prisma: PrismaPostgresService) { }
|
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 THIRTY_DAYS = 1000 * 60 * 60 * 24 * 30;
|
||||||
const FOURTEEN_DAYS = 1000 * 60 * 60 * 24 * 14;
|
const FOURTEEN_DAYS = 1000 * 60 * 60 * 24 * 14;
|
||||||
const today = new Date();
|
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()) {
|
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);
|
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());
|
const year_difference = today.getFullYear() - (pto_details!.last_updated?.getFullYear() ?? today.getFullYear());
|
||||||
|
|
@ -69,7 +71,10 @@ export class SickLeaveService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new PTO row
|
// 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 {
|
try {
|
||||||
const new_pto_entry = await this.prisma.paidTimeOff.create({
|
const new_pto_entry = await this.prisma.paidTimeOff.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -88,12 +93,16 @@ export class SickLeaveService {
|
||||||
|
|
||||||
return { success: true, data: new_pto_entry };
|
return { success: true, data: new_pto_entry };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error };
|
return { success: false, error: '' + error };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add n number of sick PTO hours to employee PTO
|
// 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 {
|
try {
|
||||||
const update_pto = await this.prisma.paidTimeOff.update({
|
const update_pto = await this.prisma.paidTimeOff.update({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -107,11 +116,15 @@ export class SickLeaveService {
|
||||||
|
|
||||||
return { success: true, data: update_pto };
|
return { success: true, data: update_pto };
|
||||||
} catch (error) {
|
} 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' };
|
if (asked_hours <= 0) return { success: false, error: 'INVALID_BANKING_HOURS' };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -154,74 +167,9 @@ export class SickLeaveService {
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return { success: false, error: 'INVALID_BANKING_SHIFT' };
|
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,
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
//switch employeeId for email
|
|
||||||
async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise<Result<number, string>> {
|
async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise<Result<number, string>> {
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
if (!employee_id.success) return { success: false, error: employee_id.error }
|
if (!employee_id.success) return { success: false, error: employee_id.error }
|
||||||
|
|
@ -20,7 +19,7 @@ export class VacationService {
|
||||||
where: { id: employee_id.data },
|
where: { id: employee_id.data },
|
||||||
select: { first_work_day: true },
|
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;
|
const hire_date = employee.first_work_day;
|
||||||
|
|
||||||
|
|
@ -110,10 +109,8 @@ export class VacationService {
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return { success: false, error: 'INVALID_VACATION_SHIFT' }
|
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 { ExpenseDto } from "src/time-and-attendance/expenses/expense-create.dto";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
|
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
|
||||||
|
|
@ -18,7 +18,10 @@ export class ExpenseController {
|
||||||
|
|
||||||
@Post('create')
|
@Post('create')
|
||||||
@ModuleAccessAllowed(ModulesEnum.timesheets)
|
@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');
|
if (!email) throw new UnauthorizedException('Unauthorized User');
|
||||||
return this.createService.createExpense(dto, email);
|
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 { ExpenseDto } from "src/time-and-attendance/expenses/expense-create.dto";
|
||||||
import { NormalizedExpense } from "src/time-and-attendance/utils/type.utils";
|
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
|
//makes sure that comments are the right length the date is of Date type
|
||||||
export const normalizeAndParseExpenseDto = async (dto: ExpenseDto): Promise<Result<NormalizedExpense, string>> => {
|
export const normalizeAndParseExpenseDto = async (dto: ExpenseDto): Promise<Result<NormalizedExpense, string>> => {
|
||||||
const mileage = parseOptionalNumber(dto.mileage, "mileage");
|
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";
|
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [ExpenseController],
|
controllers: [
|
||||||
|
ExpenseController
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ExpenseCreateService,
|
ExpenseCreateService,
|
||||||
ExpenseUpdateService,
|
ExpenseUpdateService,
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ export class ExpenseCreateService {
|
||||||
private readonly payPeriodEventService: PayPeriodEventService,
|
private readonly payPeriodEventService: PayPeriodEventService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
//_________________________________________________________________
|
async createExpense(
|
||||||
// CREATE
|
dto: ExpenseDto,
|
||||||
//_________________________________________________________________
|
email: string
|
||||||
async createExpense(dto: ExpenseDto, email: string): Promise<Result<ExpenseDto, string>> {
|
): Promise<Result<ExpenseDto, string>> {
|
||||||
try {
|
try {
|
||||||
//fetch employee_id using req.user.email
|
//fetch employee_id using req.user.email
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
|
|
@ -75,6 +75,7 @@ export class ExpenseCreateService {
|
||||||
|
|
||||||
return { success: true, data: created };
|
return { success: true, data: created };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return { success: false, error: 'INVALID_EXPENSE' };
|
return { success: false, error: 'INVALID_EXPENSE' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ export class ExpenseDeleteService {
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
//_________________________________________________________________
|
async deleteExpense(
|
||||||
// DELETE
|
expense_id: number,
|
||||||
//_________________________________________________________________
|
email: string
|
||||||
async deleteExpense(expense_id: number, email: string): Promise<Result<number, string>> {
|
): Promise<Result<number, string>> {
|
||||||
// get employee id of employee who made delete request
|
// get employee id of employee who made delete request
|
||||||
const employee = await this.emailResolver.findIdByEmail(email);
|
const employee = await this.emailResolver.findIdByEmail(email);
|
||||||
|
|
||||||
|
|
@ -56,6 +56,7 @@ export class ExpenseDeleteService {
|
||||||
|
|
||||||
return { success: true, data: expense_id };
|
return { success: true, data: expense_id };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return { success: false, error: `EXPENSE_NOT_FOUND` };
|
return { success: false, error: `EXPENSE_NOT_FOUND` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,12 @@ export class ExpenseUpdateService {
|
||||||
private readonly typeResolver: BankCodesResolver,
|
private readonly typeResolver: BankCodesResolver,
|
||||||
private readonly payPeriodEventService: PayPeriodEventService,
|
private readonly payPeriodEventService: PayPeriodEventService,
|
||||||
) { }
|
) { }
|
||||||
//_________________________________________________________________
|
|
||||||
// UPDATE
|
async updateExpense(
|
||||||
//_________________________________________________________________
|
dto: ExpenseDto,
|
||||||
async updateExpense(dto: ExpenseDto, email: string, employee_email?: string): Promise<Result<ExpenseDto, string>> {
|
email: string,
|
||||||
|
employee_email?: string
|
||||||
|
): Promise<Result<ExpenseDto, string>> {
|
||||||
try {
|
try {
|
||||||
const account_email = employee_email ?? email;
|
const account_email = employee_email ?? email;
|
||||||
//fetch employee_id using req.user.email
|
//fetch employee_id using req.user.email
|
||||||
|
|
@ -79,6 +81,7 @@ export class ExpenseUpdateService {
|
||||||
|
|
||||||
return { success: true, data: updated };
|
return { success: true, data: updated };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return { success: false, error: 'EXPENSE_NOT_FOUND' };
|
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({
|
response.set({
|
||||||
'Content-Type': 'text/csv; charset=utf-8',
|
'Content-Type': 'text/csv; charset=utf-8',
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
HolidayService,
|
HolidayService,
|
||||||
EmailToIdResolver,
|
EmailToIdResolver,
|
||||||
],
|
],
|
||||||
controllers: [CsvExportController],
|
controllers: [
|
||||||
|
CsvExportController
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CsvExportModule { }
|
export class CsvExportModule { }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export const consolidateRowHoursAndAmountByType = (rows: InternalCsvRow[]): Inte
|
||||||
const map = new Map<string, InternalCsvRow>();
|
const map = new Map<string, InternalCsvRow>();
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (row.code = VACATION) {
|
if (row.code === VACATION) {
|
||||||
map.set(`${row.code}|${row.shift_date}`, row);
|
map.set(`${row.code}|${row.shift_date}`, row);
|
||||||
} else {
|
} else {
|
||||||
const key = `${row.code}|${row.semaine_no}`;
|
const key = `${row.code}|${row.semaine_no}`;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import { CsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CsvGeneratorService {
|
export class CsvGeneratorService {
|
||||||
//csv builder and "mise en page"
|
//csv builder and "mise en page"
|
||||||
generateCsv(rows: CsvRow[]): Buffer {
|
generateCsv(
|
||||||
|
rows: CsvRow[]
|
||||||
|
): Buffer {
|
||||||
const body = rows.map(row => {
|
const body = rows.map(row => {
|
||||||
const quantity_hours = (typeof row.quantite_hre === 'number') ? row.quantite_hre.toFixed(2) : '';
|
const quantity_hours = (typeof row.quantite_hre === 'number') ? row.quantite_hre.toFixed(2) : '';
|
||||||
const amount = (typeof row.montant === 'number') ? row.montant.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
|
* @returns The desired filtered data in semi-colon-separated format, grouped and sorted by
|
||||||
* employee and by bank codes.
|
* 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[] = [];
|
const BILLABLE_SHIFT_TYPES: BillableShiftType[] = [];
|
||||||
|
|
||||||
if (filters.types.shifts) BILLABLE_SHIFT_TYPES.push('REGULAR', 'OVERTIME', 'EMERGENCY', 'EVENING', 'SICK');
|
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 rows: InternalCsvRow[] = exportedShifts.map(shift => {
|
||||||
const employee = shift!.timesheet.employee;
|
const employee = shift.timesheet.employee;
|
||||||
const week = computeWeekNumber(start, shift!.date);
|
const week = computeWeekNumber(start, shift.date);
|
||||||
const type_transaction = shift!.bank_code.bank_code.charAt(0);
|
const type_transaction = shift.bank_code.bank_code.charAt(0);
|
||||||
const code = Number(shift!.bank_code.bank_code.slice(1,));
|
const code = Number(shift.bank_code.bank_code.slice(1,));
|
||||||
const isPTO = PTO_SHIFT_CODES.includes(shift.bank_code.bank_code)
|
const isPTO = PTO_SHIFT_CODES.includes(shift.bank_code.bank_code)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -84,7 +88,7 @@ export class CsvExportService {
|
||||||
releve: 0,
|
releve: 0,
|
||||||
type_transaction: type_transaction,
|
type_transaction: type_transaction,
|
||||||
code: code,
|
code: code,
|
||||||
quantite_hre: computeHours(shift!.start_time, shift!.end_time),
|
quantite_hre: computeHours(shift.start_time, shift.end_time),
|
||||||
taux_horaire: '',
|
taux_horaire: '',
|
||||||
montant: undefined,
|
montant: undefined,
|
||||||
semaine_no: week,
|
semaine_no: week,
|
||||||
|
|
@ -93,8 +97,8 @@ export class CsvExportService {
|
||||||
departem_no: undefined,
|
departem_no: undefined,
|
||||||
sous_departem_no: undefined,
|
sous_departem_no: undefined,
|
||||||
date_transaction: formatDate(end),
|
date_transaction: formatDate(end),
|
||||||
premier_jour_absence: isPTO ? formatDate(shift!.date) : '',
|
premier_jour_absence: isPTO ? formatDate(shift.date) : '',
|
||||||
dernier_jour_absence: isPTO ? formatDate(shift!.date) : '',
|
dernier_jour_absence: isPTO ? formatDate(shift.date) : '',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -110,8 +114,8 @@ export class CsvExportService {
|
||||||
|
|
||||||
exportedExpenses.map(expense => {
|
exportedExpenses.map(expense => {
|
||||||
const employee = expense.timesheet.employee;
|
const employee = expense.timesheet.employee;
|
||||||
const type_transaction = expense!.bank_code.bank_code.charAt(0);
|
const type_transaction = expense.bank_code.bank_code.charAt(0);
|
||||||
const code = Number(expense!.bank_code.bank_code.slice(1,))
|
const code = Number(expense.bank_code.bank_code.slice(1,))
|
||||||
const week = computeWeekNumber(start, expense.date);
|
const week = computeWeekNumber(start, expense.date);
|
||||||
|
|
||||||
rows.push({
|
rows.push({
|
||||||
|
|
@ -147,7 +151,7 @@ export class CsvExportService {
|
||||||
});
|
});
|
||||||
|
|
||||||
const holiday_rows = await applyHolidayRequalifications(rows, this.holiday_service, HOLIDAY_SHIFT_CODE[0]);
|
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
|
//requalifies regular hours into overtime when needed
|
||||||
const requalified_rows = await applyOvertimeRequalifications(consolidated_rows, this.overtime_service);
|
const requalified_rows = await applyOvertimeRequalifications(consolidated_rows, this.overtime_service);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ export type NormalizedExpense = {
|
||||||
amount?: number | Prisma.Decimal | null;
|
amount?: number | Prisma.Decimal | null;
|
||||||
mileage?: number | Prisma.Decimal | null;
|
mileage?: number | Prisma.Decimal | null;
|
||||||
attachment?: number;
|
attachment?: number;
|
||||||
// bank_code_id: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NormalizedLeaveRequest = {
|
export type NormalizedLeaveRequest = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user