From e00c6027684b628426833749eca29df3505ae769 Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Wed, 22 Jan 2025 19:21:04 +0200 Subject: [PATCH 01/16] Implementation of employee weekly schedule planning --- packages/contracts/src/index.ts | 1 + .../src/lib/employee-availability.model.ts | 56 +++++++++ .../core/src/lib/core/entities/internal.ts | 1 + ...ployee-availability.bulk.create.command.ts | 8 ++ .../employee-availability.create.command.ts | 8 ++ ...ployee-availability.bulk.create.handler.ts | 27 +++++ .../employee-availability.create.handler.ts | 25 ++++ .../commands/handlers/index.ts | 2 + .../employee-availability/commands/index.ts | 4 + .../employee-availability.controller.ts | 110 ++++++++++++++++++ .../employee-availability.entity.ts | 69 +++++++++++ .../employee-availability.module.ts | 9 ++ .../employee-availability.service.ts | 14 +++ .../src/lib/employee-availability/index.ts | 3 + ...ro-orm-employee-availability.repository.ts | 4 + ...pe-orm-employee-availability.repository.ts | 11 ++ .../core/src/lib/employee/employee.entity.ts | 13 ++- .../employee-availability-status.pipe.ts | 48 ++++++++ 18 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 packages/contracts/src/lib/employee-availability.model.ts create mode 100644 packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts create mode 100644 packages/core/src/lib/employee-availability/commands/employee-availability.create.command.ts create mode 100644 packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts create mode 100644 packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts create mode 100644 packages/core/src/lib/employee-availability/commands/handlers/index.ts create mode 100644 packages/core/src/lib/employee-availability/commands/index.ts create mode 100644 packages/core/src/lib/employee-availability/employee-availability.controller.ts create mode 100644 packages/core/src/lib/employee-availability/employee-availability.entity.ts create mode 100644 packages/core/src/lib/employee-availability/employee-availability.module.ts create mode 100644 packages/core/src/lib/employee-availability/employee-availability.service.ts create mode 100644 packages/core/src/lib/employee-availability/index.ts create mode 100644 packages/core/src/lib/employee-availability/repository/micro-orm-employee-availability.repository.ts create mode 100644 packages/core/src/lib/employee-availability/repository/type-orm-employee-availability.repository.ts create mode 100644 packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index 6ec175dde0a..c879cc2e869 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -40,6 +40,7 @@ export * from './lib/email-reset.model'; export * from './lib/email-template.model'; export * from './lib/email.model'; export * from './lib/employee-appointment.model'; +export * from './lib/employee-availability.model'; export * from './lib/employee-award.model'; export * from './lib/employee-job.model'; export * from './lib/employee-phone.model'; diff --git a/packages/contracts/src/lib/employee-availability.model.ts b/packages/contracts/src/lib/employee-availability.model.ts new file mode 100644 index 00000000000..8bf9b223e70 --- /dev/null +++ b/packages/contracts/src/lib/employee-availability.model.ts @@ -0,0 +1,56 @@ +import { IBasePerTenantAndOrganizationEntityModel } from './base-entity.model'; +import { IEmployee } from './employee.model'; +import { IOrganization } from './organization.model'; + +/** + * Enum representing the availability status of an employee. + */ +export enum AvailabilityStatusEnum { + Available = 'Available', + Partial = 'Partial', + Unavailable = 'Unavailable' +} + +export interface IEmployeeAvailability extends IBasePerTenantAndOrganizationEntityModel { + employee: IEmployee; + employeeId: string; + startDate: Date; + endDate: Date; + dayOfWeek: number; // 0 = Sunday, 6 = Saturday + availabilityStatus: AvailabilityStatusEnum; + availabilityNotes?: string; +} + +/** + * Input interface for finding Employee Availability records. + */ +export interface IEmployeeAvailabilityFindInput { + employeeId?: string; + availabilityStatus?: AvailabilityStatusEnum; + startDate?: Date; + endDate?: Date; +} + +/** + * Input interface for creating new Employee Availability records. + */ +export interface IEmployeeAvailabilityCreateInput extends IBasePerTenantAndOrganizationEntityModel { + employeeId: string; + startDate: Date; + endDate: Date; + dayOfWeek: number; // 0 = Sunday, 6 = Saturday + availabilityStatus: AvailabilityStatusEnum; + availabilityNotes?: string; +} + +/** + * Input interface for updating Employee Availability records. + */ +export interface IEmployeeAvailabilityUpdateInput extends IBasePerTenantAndOrganizationEntityModel { + employeeId?: string; + startDate?: Date; + endDate?: Date; + dayOfWeek?: number; // 0 = Sunday, 6 = Saturday + availabilityStatus?: AvailabilityStatusEnum; + availabilityNotes?: string; +} diff --git a/packages/core/src/lib/core/entities/internal.ts b/packages/core/src/lib/core/entities/internal.ts index 99acc3ce699..42bf5c166b6 100644 --- a/packages/core/src/lib/core/entities/internal.ts +++ b/packages/core/src/lib/core/entities/internal.ts @@ -36,6 +36,7 @@ export * from '../../email-history/email-history.entity'; export * from '../../email-reset/email-reset.entity'; export * from '../../email-template/email-template.entity'; export * from '../../employee-appointment/employee-appointment.entity'; +export * from '../../employee-availability/employee-availability.entity'; export * from '../../employee-award/employee-award.entity'; export * from '../../employee-level/employee-level.entity'; export * from '../../employee-phone/employee-phone.entity'; diff --git a/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts new file mode 100644 index 00000000000..edd1e7a5798 --- /dev/null +++ b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts @@ -0,0 +1,8 @@ +import { ICommand } from '@nestjs/cqrs'; +import { IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; + +export class EmployeeAvailabilityBulkCreateCommand implements ICommand { + static readonly type = '[Employee Bulk Availability ] Register'; + + constructor(public readonly input: IEmployeeAvailabilityCreateInput[]) {} +} diff --git a/packages/core/src/lib/employee-availability/commands/employee-availability.create.command.ts b/packages/core/src/lib/employee-availability/commands/employee-availability.create.command.ts new file mode 100644 index 00000000000..86ad58300f4 --- /dev/null +++ b/packages/core/src/lib/employee-availability/commands/employee-availability.create.command.ts @@ -0,0 +1,8 @@ +import { ICommand } from '@nestjs/cqrs'; +import { IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; + +export class EmployeeAvailabilityCreateCommand implements ICommand { + static readonly type = '[EmployeeAvailability] Create'; + + constructor(public readonly input: IEmployeeAvailabilityCreateInput) {} +} diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts new file mode 100644 index 00000000000..4fe94b13aac --- /dev/null +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts @@ -0,0 +1,27 @@ +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { IEmployeeAvailability } from '@gauzy/contracts'; +import { RequestContext } from '../../../core/context'; +import { EmployeeAvailabilityBulkCreateCommand } from '../employee-availability.bulk.create.command'; +import { EmployeeAvailability } from '../../employee-availability.entity'; +import { EmployeeAvailabilityCreateCommand } from '../employee-availability.create.command'; + +@CommandHandler(EmployeeAvailabilityBulkCreateCommand) +export class EmployeeAvailabilityBulkCreateHandler implements ICommandHandler { + constructor(private readonly commandBus: CommandBus) {} + + public async execute(command: EmployeeAvailabilityBulkCreateCommand): Promise { + const { input } = command; + const allAvailability: IEmployeeAvailability[] = []; + const tenantId = RequestContext.currentTenantId(); + + for (const item of input) { + let availability = new EmployeeAvailability({ + ...item, + tenantId + }); + availability = await this.commandBus.execute(new EmployeeAvailabilityCreateCommand(availability)); + allAvailability.push(availability); + } + return allAvailability; + } +} diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts new file mode 100644 index 00000000000..5202ea87784 --- /dev/null +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts @@ -0,0 +1,25 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { IEmployeeAvailability } from '@gauzy/contracts'; +import { RequestContext } from '../../../core/context'; +import { EmployeeAvailabilityService } from '../../employee-availability.service'; +import { EmployeeAvailability } from '../../employee-availability.entity'; +import { EmployeeAvailabilityCreateCommand } from '../employee-availability.create.command'; + +@CommandHandler(EmployeeAvailabilityCreateCommand) +export class EmployeeAvailabilityCreateHandler implements ICommandHandler { + constructor(private readonly availabilityService: EmployeeAvailabilityService) {} + + public async execute(command: EmployeeAvailabilityCreateCommand): Promise { + const { input } = command; + const { startDate, endDate } = input; + if (!startDate || !endDate) return; + const tenantId = RequestContext.currentTenantId(); + + const availability = new EmployeeAvailability({ + ...input, + tenantId + }); + + return await this.availabilityService.create(availability); + } +} diff --git a/packages/core/src/lib/employee-availability/commands/handlers/index.ts b/packages/core/src/lib/employee-availability/commands/handlers/index.ts new file mode 100644 index 00000000000..68c458801b7 --- /dev/null +++ b/packages/core/src/lib/employee-availability/commands/handlers/index.ts @@ -0,0 +1,2 @@ +export * from './employee-availability.bulk.create.handler'; +export * from './employee-availability.create.handler'; diff --git a/packages/core/src/lib/employee-availability/commands/index.ts b/packages/core/src/lib/employee-availability/commands/index.ts new file mode 100644 index 00000000000..549814fe287 --- /dev/null +++ b/packages/core/src/lib/employee-availability/commands/index.ts @@ -0,0 +1,4 @@ +export * from './employee-availability.bulk.create.command'; +export * from './employee-availability.create.command'; +export * from './handlers/employee-availability.bulk.create.handler'; +export * from './handlers/employee-availability.create.handler'; diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts new file mode 100644 index 00000000000..39d66ccd36a --- /dev/null +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -0,0 +1,110 @@ +import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put, Query, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { EmployeeAvailabilityService } from './employee-availability.service'; +import { EmployeeAvailability } from './employee-availability.entity'; +import { CrudController } from '../core'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ParseJsonPipe, TenantPermissionGuard, UUIDValidationPipe } from '../shared'; +import { ID, IEmployeeAvailability, IEmployeeAvailabilityCreateInput, IPagination } from '@gauzy/contracts'; +import { UpdateResult } from 'typeorm'; +import { EmployeeAvailabilityBulkCreateCommand, EmployeeAvailabilityCreateCommand } from './commands'; + +@ApiTags('EmployeeAvailability') +@UseGuards(TenantPermissionGuard) +@Controller('/employee-availability') +export class EmployeeAvailabilityController extends CrudController { + constructor( + private readonly availabilityService: EmployeeAvailabilityService, + private readonly commandBus: CommandBus + ) { + super(availabilityService); + } + + /** + * Create multiple employee availability records in bulk. + * + * @param entities List of availability records to create + * @returns The created availability records + */ + @ApiOperation({ summary: 'Create multiple availability records' }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'The records have been successfully created.' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input. The response body may contain clues as to what went wrong.' + }) + @HttpCode(HttpStatus.CREATED) + @Post('/bulk') + async createBulk(@Body() entities: IEmployeeAvailabilityCreateInput[]): Promise { + return await this.commandBus.execute(new EmployeeAvailabilityBulkCreateCommand(entities)); + } + + /** + * Retrieve all employee availability records. + * + * @param data Query parameters, including relations and filters + * @returns A paginated list of availability records + */ + @ApiOperation({ summary: 'Retrieve all availability records' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Successfully retrieved availability records.' + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'No availability records found.' + }) + @Get() + async findAll(@Query('data', ParseJsonPipe) data: any): Promise> { + const { relations, findInput } = data; + return this.availabilityService.findAll({ where: findInput, relations }); + } + + /** + * Create a new employee availability record. + * + * @param entity The data for the new availability record + * @returns The created availability record + */ + @ApiOperation({ summary: 'Create a new availability record' }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'The record has been successfully created.' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input. The response body may contain clues as to what went wrong.' + }) + @HttpCode(HttpStatus.CREATED) + @Post() + async create(@Body() entity: IEmployeeAvailabilityCreateInput): Promise { + return await this.commandBus.execute(new EmployeeAvailabilityCreateCommand(entity)); + } + + /** + * Update an existing employee availability record by its ID. + * + * @param id The ID of the availability record + * @param entity The updated data for the record + * @returns The updated availability record + */ + @ApiOperation({ summary: 'Update an existing availability record' }) + @ApiResponse({ + status: HttpStatus.ACCEPTED, + description: 'The record has been successfully updated.' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input. The response body may contain clues as to what went wrong.' + }) + @HttpCode(HttpStatus.ACCEPTED) + @Put(':id') + async update( + @Param('id', UUIDValidationPipe) id: ID, + @Body() entity: IEmployeeAvailability + ): Promise { + return this.availabilityService.update(id, { ...entity }); + } +} diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts new file mode 100644 index 00000000000..5561322e4e6 --- /dev/null +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -0,0 +1,69 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsDate, IsInt, IsOptional, IsEnum } from 'class-validator'; +import { JoinColumn, RelationId } from 'typeorm'; +import { AvailabilityStatusEnum, ID, IEmployeeAvailability } from '@gauzy/contracts'; +import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; +import { MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from './../core/decorators/entity'; +import { AvailabilityStatusTransformer } from '../shared/pipes/employee-availability-status.pipe'; + +@MultiORMEntity('employee_availability') +export class EmployeeAvailability extends TenantOrganizationBaseEntity implements IEmployeeAvailability { + @ApiProperty({ type: () => Date }) + @IsDate() + @IsNotEmpty() + @MultiORMColumn() + startDate: Date; + + @ApiProperty({ type: () => Date }) + @IsDate() + @IsNotEmpty() + @MultiORMColumn() + endDate: Date; + + @ApiProperty({ type: () => Number, description: 'Day of the week (0 = Sunday, 6 = Saturday)' }) + @IsInt() + @IsNotEmpty() + @MultiORMColumn({ type: 'int', check: 'day_of_week BETWEEN 0 AND 6' }) + dayOfWeek: number; + + @ApiProperty({ enum: AvailabilityStatusEnum }) + @IsOptional() + @IsEnum(AvailabilityStatusEnum) + @MultiORMColumn({ + type: 'int', + nullable: true, + transformer: new AvailabilityStatusTransformer() + }) + availabilityStatus: AvailabilityStatusEnum; + + @ApiPropertyOptional({ + type: () => String, + description: 'Optional notes (e.g., "Available until 2 PM")', + required: false + }) + @IsString() + @IsOptional() + @MultiORMColumn({ type: 'text', nullable: true }) + availabilityNotes?: string; + + /* + |-------------------------------------------------------------------------- + | @ManyToOne + |-------------------------------------------------------------------------- + */ + + /** + * Employee + */ + @MultiORMManyToOne(() => Employee, { + nullable: false, + onDelete: 'CASCADE' + }) + @JoinColumn() + employee: Employee; + + @ApiProperty({ type: () => String }) + @RelationId((it: EmployeeAvailability) => it.employee) + @MultiORMColumn({ type: 'uuid' }) + employeeId: ID; +} diff --git a/packages/core/src/lib/employee-availability/employee-availability.module.ts b/packages/core/src/lib/employee-availability/employee-availability.module.ts new file mode 100644 index 00000000000..09dcd9d118b --- /dev/null +++ b/packages/core/src/lib/employee-availability/employee-availability.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { EmployeeAvailabilityService } from './employee-availability.service'; +import { EmployeeAvailabilityController } from './employee-availability.controller'; + +@Module({ + providers: [EmployeeAvailabilityService], + controllers: [EmployeeAvailabilityController] +}) +export class EmployeeAvailabilityModule {} diff --git a/packages/core/src/lib/employee-availability/employee-availability.service.ts b/packages/core/src/lib/employee-availability/employee-availability.service.ts new file mode 100644 index 00000000000..215e962956e --- /dev/null +++ b/packages/core/src/lib/employee-availability/employee-availability.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { TypeOrmEmployeeAvailabilityRepository } from './repository/type-orm-employee-availability.repository'; +import { MikroOrmEmployeeAvailabilityRepository } from './repository/micro-orm-employee-availability.repository'; +import { EmployeeAvailability, TenantAwareCrudService } from '../core'; + +@Injectable() +export class EmployeeAvailabilityService extends TenantAwareCrudService { + constructor( + typeOrmEmployeeAvailabilityRepository: TypeOrmEmployeeAvailabilityRepository, + mikroOrmEmployeeAvailabilityRepository: MikroOrmEmployeeAvailabilityRepository + ) { + super(typeOrmEmployeeAvailabilityRepository, mikroOrmEmployeeAvailabilityRepository); + } +} diff --git a/packages/core/src/lib/employee-availability/index.ts b/packages/core/src/lib/employee-availability/index.ts new file mode 100644 index 00000000000..c4cf0931109 --- /dev/null +++ b/packages/core/src/lib/employee-availability/index.ts @@ -0,0 +1,3 @@ +export { EmployeeAvailability } from './employee-availability.entity'; +export { EmployeeAvailabilityModule } from './employee-availability.module'; +export { EmployeeAvailabilityService } from './employee-availability.service'; diff --git a/packages/core/src/lib/employee-availability/repository/micro-orm-employee-availability.repository.ts b/packages/core/src/lib/employee-availability/repository/micro-orm-employee-availability.repository.ts new file mode 100644 index 00000000000..aa4de6314e1 --- /dev/null +++ b/packages/core/src/lib/employee-availability/repository/micro-orm-employee-availability.repository.ts @@ -0,0 +1,4 @@ +import { MikroOrmBaseEntityRepository } from '../../core/repository/mikro-orm-base-entity.repository'; +import { EmployeeAvailability } from '../employee-availability.entity'; + +export class MikroOrmEmployeeAvailabilityRepository extends MikroOrmBaseEntityRepository {} diff --git a/packages/core/src/lib/employee-availability/repository/type-orm-employee-availability.repository.ts b/packages/core/src/lib/employee-availability/repository/type-orm-employee-availability.repository.ts new file mode 100644 index 00000000000..1b3bec31f48 --- /dev/null +++ b/packages/core/src/lib/employee-availability/repository/type-orm-employee-availability.repository.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { EmployeeAvailability } from '../employee-availability.entity'; + +@Injectable() +export class TypeOrmEmployeeAvailabilityRepository extends Repository { + constructor(@InjectRepository(EmployeeAvailability) readonly repository: Repository) { + super(repository.target, repository.manager, repository.queryRunner); + } +} diff --git a/packages/core/src/lib/employee/employee.entity.ts b/packages/core/src/lib/employee/employee.entity.ts index 4a0dd0b31fc..414c8a39f97 100644 --- a/packages/core/src/lib/employee/employee.entity.ts +++ b/packages/core/src/lib/employee/employee.entity.ts @@ -37,7 +37,8 @@ import { ID, IFavorite, IComment, - IOrganizationSprint + IOrganizationSprint, + IEmployeeAvailability } from '@gauzy/contracts'; import { ColumnIndex, @@ -55,6 +56,7 @@ import { Comment, Contact, DailyPlan, + EmployeeAvailability, EmployeeAward, EmployeePhone, EmployeeSetting, @@ -541,6 +543,15 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee, }) modules?: IOrganizationProjectModule[]; + /** + * One-to-many relationship with EmployeeAvailability. + * An employee can have multiple availability records. + */ + @MultiORMOneToMany(() => EmployeeAvailability, (availability) => availability.employee, { + cascade: true + }) + availabilities?: IEmployeeAvailability[]; + /** * Estimations */ diff --git a/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts b/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts new file mode 100644 index 00000000000..3e7b41dc265 --- /dev/null +++ b/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts @@ -0,0 +1,48 @@ +import { AvailabilityStatusEnum } from '@gauzy/contracts'; +import { ValueTransformer } from 'typeorm'; + +/** + * Transformer to handle the conversion between the enum values + * (used in the application) and integer values (stored in the database). + */ +export class AvailabilityStatusTransformer implements ValueTransformer { + /** + * Converts the enum value to its corresponding integer representation + * when saving to the database. + * + * @param value - The `AvailabilityStatusEnum` value. + * @returns The corresponding integer value for storage in the database. + */ + to(value: AvailabilityStatusEnum): number { + switch (value) { + case AvailabilityStatusEnum.Available: + return 0; + case AvailabilityStatusEnum.Partial: + return 1; + case AvailabilityStatusEnum.Unavailable: + return 2; + default: + return null; + } + } + + /** + * Converts the integer value from the database back to the corresponding + * enum value when reading. + * + * @param value - The integer value stored in the database. + * @returns The corresponding `AvailabilityStatusEnum` value. + */ + from(value: number): AvailabilityStatusEnum { + switch (value) { + case 0: + return AvailabilityStatusEnum.Available; + case 1: + return AvailabilityStatusEnum.Partial; + case 2: + return AvailabilityStatusEnum.Unavailable; + default: + return null; + } + } +} From 7c4e4f5ce4bafd2026d32fb01d64c486ea007e8f Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Thu, 23 Jan 2025 14:24:30 +0200 Subject: [PATCH 02/16] Feedback integration --- .../src/lib/employee-availability.model.ts | 11 +++++------ ...employee-availability.bulk.create.command.ts | 2 +- .../employee-availability.create.handler.ts | 5 ++++- .../employee-availability.controller.ts | 17 +++++++++-------- .../employee-availability.entity.ts | 9 +++------ .../employee-availability.service.ts | 3 ++- .../pipes/employee-availability-status.pipe.ts | 2 +- 7 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/contracts/src/lib/employee-availability.model.ts b/packages/contracts/src/lib/employee-availability.model.ts index 8bf9b223e70..f19c1d497d4 100644 --- a/packages/contracts/src/lib/employee-availability.model.ts +++ b/packages/contracts/src/lib/employee-availability.model.ts @@ -1,6 +1,5 @@ -import { IBasePerTenantAndOrganizationEntityModel } from './base-entity.model'; +import { IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model'; import { IEmployee } from './employee.model'; -import { IOrganization } from './organization.model'; /** * Enum representing the availability status of an employee. @@ -13,7 +12,7 @@ export enum AvailabilityStatusEnum { export interface IEmployeeAvailability extends IBasePerTenantAndOrganizationEntityModel { employee: IEmployee; - employeeId: string; + employeeId: ID; startDate: Date; endDate: Date; dayOfWeek: number; // 0 = Sunday, 6 = Saturday @@ -25,7 +24,7 @@ export interface IEmployeeAvailability extends IBasePerTenantAndOrganizationEnti * Input interface for finding Employee Availability records. */ export interface IEmployeeAvailabilityFindInput { - employeeId?: string; + employeeId?: ID; availabilityStatus?: AvailabilityStatusEnum; startDate?: Date; endDate?: Date; @@ -35,7 +34,7 @@ export interface IEmployeeAvailabilityFindInput { * Input interface for creating new Employee Availability records. */ export interface IEmployeeAvailabilityCreateInput extends IBasePerTenantAndOrganizationEntityModel { - employeeId: string; + employeeId: ID; startDate: Date; endDate: Date; dayOfWeek: number; // 0 = Sunday, 6 = Saturday @@ -47,7 +46,7 @@ export interface IEmployeeAvailabilityCreateInput extends IBasePerTenantAndOrgan * Input interface for updating Employee Availability records. */ export interface IEmployeeAvailabilityUpdateInput extends IBasePerTenantAndOrganizationEntityModel { - employeeId?: string; + employeeId?: ID; startDate?: Date; endDate?: Date; dayOfWeek?: number; // 0 = Sunday, 6 = Saturday diff --git a/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts index edd1e7a5798..fed2de77fec 100644 --- a/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts +++ b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts @@ -2,7 +2,7 @@ import { ICommand } from '@nestjs/cqrs'; import { IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; export class EmployeeAvailabilityBulkCreateCommand implements ICommand { - static readonly type = '[Employee Bulk Availability ] Register'; + static readonly type = '[Employee Bulk Availability ] Create'; constructor(public readonly input: IEmployeeAvailabilityCreateInput[]) {} } diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts index 5202ea87784..9c4c8cd7660 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts @@ -4,6 +4,7 @@ import { RequestContext } from '../../../core/context'; import { EmployeeAvailabilityService } from '../../employee-availability.service'; import { EmployeeAvailability } from '../../employee-availability.entity'; import { EmployeeAvailabilityCreateCommand } from '../employee-availability.create.command'; +import { BadRequestException } from '@nestjs/common'; @CommandHandler(EmployeeAvailabilityCreateCommand) export class EmployeeAvailabilityCreateHandler implements ICommandHandler { @@ -12,7 +13,9 @@ export class EmployeeAvailabilityCreateHandler implements ICommandHandler { const { input } = command; const { startDate, endDate } = input; - if (!startDate || !endDate) return; + if (!startDate || !endDate) { + throw new BadRequestException('Start date and end date are required.'); + } const tenantId = RequestContext.currentTenantId(); const availability = new EmployeeAvailability({ diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts index 39d66ccd36a..3f5b12eceb8 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.controller.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -1,12 +1,12 @@ +import { UpdateResult } from 'typeorm'; import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put, Query, UseGuards } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { EmployeeAvailabilityService } from './employee-availability.service'; -import { EmployeeAvailability } from './employee-availability.entity'; -import { CrudController } from '../core'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ParseJsonPipe, TenantPermissionGuard, UUIDValidationPipe } from '../shared'; import { ID, IEmployeeAvailability, IEmployeeAvailabilityCreateInput, IPagination } from '@gauzy/contracts'; -import { UpdateResult } from 'typeorm'; +import { EmployeeAvailabilityService } from './employee-availability.service'; +import { EmployeeAvailability } from './employee-availability.entity'; +import { CrudController, PaginationParams } from '../core'; +import { TenantPermissionGuard, UUIDValidationPipe } from '../shared'; import { EmployeeAvailabilityBulkCreateCommand, EmployeeAvailabilityCreateCommand } from './commands'; @ApiTags('EmployeeAvailability') @@ -57,9 +57,10 @@ export class EmployeeAvailabilityController extends CrudController> { - const { relations, findInput } = data; - return this.availabilityService.findAll({ where: findInput, relations }); + async findAll( + @Query() filter: PaginationParams + ): Promise> { + return this.availabilityService.findAll(filter); } /** diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts index 5561322e4e6..33e5c522e4c 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.entity.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -23,15 +23,13 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @ApiProperty({ type: () => Number, description: 'Day of the week (0 = Sunday, 6 = Saturday)' }) @IsInt() @IsNotEmpty() - @MultiORMColumn({ type: 'int', check: 'day_of_week BETWEEN 0 AND 6' }) + @MultiORMColumn({ check: 'day_of_week BETWEEN 0 AND 6' }) dayOfWeek: number; @ApiProperty({ enum: AvailabilityStatusEnum }) @IsOptional() @IsEnum(AvailabilityStatusEnum) @MultiORMColumn({ - type: 'int', - nullable: true, transformer: new AvailabilityStatusTransformer() }) availabilityStatus: AvailabilityStatusEnum; @@ -43,7 +41,7 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement }) @IsString() @IsOptional() - @MultiORMColumn({ type: 'text', nullable: true }) + @MultiORMColumn({ nullable: true }) availabilityNotes?: string; /* @@ -56,7 +54,6 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement * Employee */ @MultiORMManyToOne(() => Employee, { - nullable: false, onDelete: 'CASCADE' }) @JoinColumn() @@ -64,6 +61,6 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @ApiProperty({ type: () => String }) @RelationId((it: EmployeeAvailability) => it.employee) - @MultiORMColumn({ type: 'uuid' }) + @MultiORMColumn() employeeId: ID; } diff --git a/packages/core/src/lib/employee-availability/employee-availability.service.ts b/packages/core/src/lib/employee-availability/employee-availability.service.ts index 215e962956e..4901b98b7f8 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.service.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@nestjs/common'; import { TypeOrmEmployeeAvailabilityRepository } from './repository/type-orm-employee-availability.repository'; import { MikroOrmEmployeeAvailabilityRepository } from './repository/micro-orm-employee-availability.repository'; -import { EmployeeAvailability, TenantAwareCrudService } from '../core'; +import { TenantAwareCrudService } from './../core/crud'; +import { EmployeeAvailability } from './employee-availability.entity'; @Injectable() export class EmployeeAvailabilityService extends TenantAwareCrudService { diff --git a/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts b/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts index 3e7b41dc265..459f3f816a2 100644 --- a/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts +++ b/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts @@ -1,5 +1,5 @@ -import { AvailabilityStatusEnum } from '@gauzy/contracts'; import { ValueTransformer } from 'typeorm'; +import { AvailabilityStatusEnum } from '@gauzy/contracts'; /** * Transformer to handle the conversion between the enum values From c369a136ecd3351654332368e7dc8fa8285c482d Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Thu, 23 Jan 2025 14:34:23 +0200 Subject: [PATCH 03/16] Feedback integration --- .../lib/employee-availability/employee-availability.entity.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts index 33e5c522e4c..6342df79e51 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.entity.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -30,6 +30,7 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @IsOptional() @IsEnum(AvailabilityStatusEnum) @MultiORMColumn({ + type: 'int', transformer: new AvailabilityStatusTransformer() }) availabilityStatus: AvailabilityStatusEnum; From f7eb738ad3715797622e9eca5f22e5c361fd503f Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Thu, 23 Jan 2025 16:54:13 +0200 Subject: [PATCH 04/16] Feedback integration --- .../src/lib/employee-availability.model.ts | 6 ++++ ...ployee-availability.bulk.create.handler.ts | 26 +++++++++++---- .../employee-availability.create.handler.ts | 20 ++++++++++-- .../dto/create-employee-availability.dto.ts | 32 +++++++++++++++++++ .../dto/update-employee-availability.dto.ts | 7 ++++ .../employee-availability.controller.ts | 8 +++-- .../employee-availability.entity.ts | 8 ++--- .../employee-availability.module.ts | 15 ++++++++- .../employee-availability-status.pipe.ts | 18 +++++------ 9 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts create mode 100644 packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts diff --git a/packages/contracts/src/lib/employee-availability.model.ts b/packages/contracts/src/lib/employee-availability.model.ts index f19c1d497d4..be613734e07 100644 --- a/packages/contracts/src/lib/employee-availability.model.ts +++ b/packages/contracts/src/lib/employee-availability.model.ts @@ -10,6 +10,12 @@ export enum AvailabilityStatusEnum { Unavailable = 'Unavailable' } +export enum AvailabilityStatusValue { + Available = 0, + Partial = 1, + Unavailable = 2 +} + export interface IEmployeeAvailability extends IBasePerTenantAndOrganizationEntityModel { employee: IEmployee; employeeId: ID; diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts index 4fe94b13aac..fb13f8f92e7 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts @@ -1,3 +1,4 @@ +import { BadRequestException } from '@nestjs/common'; import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { IEmployeeAvailability } from '@gauzy/contracts'; import { RequestContext } from '../../../core/context'; @@ -11,16 +12,27 @@ export class EmployeeAvailabilityBulkCreateHandler implements ICommandHandler { const { input } = command; + if (!Array.isArray(input) || input.length === 0) { + throw new BadRequestException('Input must be a non-empty array of availability records.'); + } + const allAvailability: IEmployeeAvailability[] = []; const tenantId = RequestContext.currentTenantId(); - for (const item of input) { - let availability = new EmployeeAvailability({ - ...item, - tenantId - }); - availability = await this.commandBus.execute(new EmployeeAvailabilityCreateCommand(availability)); - allAvailability.push(availability); + try { + // Process items in parallel with Promise.all + const results = await Promise.all( + input.map(async (item) => { + const availability = new EmployeeAvailability({ + ...item, + tenantId + }); + return this.commandBus.execute(new EmployeeAvailabilityCreateCommand(availability)); + }) + ); + allAvailability.push(...results); + } catch (error) { + throw new BadRequestException('Failed to create some availability records: ' + error.message); } return allAvailability; } diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts index 9c4c8cd7660..02c81b8cd3d 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts @@ -1,10 +1,10 @@ +import { BadRequestException } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { IEmployeeAvailability } from '@gauzy/contracts'; import { RequestContext } from '../../../core/context'; import { EmployeeAvailabilityService } from '../../employee-availability.service'; import { EmployeeAvailability } from '../../employee-availability.entity'; import { EmployeeAvailabilityCreateCommand } from '../employee-availability.create.command'; -import { BadRequestException } from '@nestjs/common'; @CommandHandler(EmployeeAvailabilityCreateCommand) export class EmployeeAvailabilityCreateHandler implements ICommandHandler { @@ -12,10 +12,26 @@ export class EmployeeAvailabilityCreateHandler implements ICommandHandler { const { input } = command; - const { startDate, endDate } = input; + const { startDate, endDate, employeeId, dayOfWeek, availabilityStatus } = input; + + if (!employeeId) { + throw new BadRequestException('Employee ID is required.'); + } + if (typeof dayOfWeek !== 'number' || dayOfWeek < 0 || dayOfWeek > 6) { + throw new BadRequestException('Day of week must be a number between 0 and 6.'); + } + if (!availabilityStatus) { + throw new BadRequestException('Availability status is required.'); + } + if (!startDate || !endDate) { throw new BadRequestException('Start date and end date are required.'); } + + if (new Date(endDate) <= new Date(startDate)) { + throw new BadRequestException('End date must be after start date.'); + } + const tenantId = RequestContext.currentTenantId(); const availability = new EmployeeAvailability({ diff --git a/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts new file mode 100644 index 00000000000..cbb2634496a --- /dev/null +++ b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts @@ -0,0 +1,32 @@ +import { IsUUID, IsDateString, IsEnum, IsOptional, IsString, IsInt } from 'class-validator'; +import { AvailabilityStatusEnum, ID, IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class CreateEmployeeAvailabilityDTO implements IEmployeeAvailabilityCreateInput { + @ApiProperty({ type: () => String }) + @IsUUID() + employeeId: ID; + + @ApiProperty({ type: () => Date }) + @IsDateString() + startDate: Date; + + @ApiProperty({ type: () => Date }) + @IsDateString() + endDate: Date; + + @ApiProperty({ type: () => Number }) + @IsInt() + dayOfWeek: number; + + @ApiProperty({ enum: AvailabilityStatusEnum }) + @IsEnum(AvailabilityStatusEnum) + availabilityStatus: AvailabilityStatusEnum; + + @ApiPropertyOptional({ + type: () => String + }) + @IsOptional() + @IsString() + availabilityNotes?: string; +} diff --git a/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts b/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts new file mode 100644 index 00000000000..8848837d6ef --- /dev/null +++ b/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts @@ -0,0 +1,7 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateEmployeeAvailabilityDTO } from './create-employee-availability.dto'; +import { IEmployeeAvailabilityUpdateInput } from '@gauzy/contracts'; + +export class UpdateEmployeeAvailabilityDTO + extends PartialType(CreateEmployeeAvailabilityDTO) + implements IEmployeeAvailabilityUpdateInput {} diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts index 3f5b12eceb8..8f16b2e7488 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.controller.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -8,6 +8,8 @@ import { EmployeeAvailability } from './employee-availability.entity'; import { CrudController, PaginationParams } from '../core'; import { TenantPermissionGuard, UUIDValidationPipe } from '../shared'; import { EmployeeAvailabilityBulkCreateCommand, EmployeeAvailabilityCreateCommand } from './commands'; +import { CreateEmployeeAvailabilityDTO } from './dto/create-employee-availability.dto'; +import { UpdateEmployeeAvailabilityDTO } from './dto/update-employee-availability.dto'; @ApiTags('EmployeeAvailability') @UseGuards(TenantPermissionGuard) @@ -37,7 +39,7 @@ export class EmployeeAvailabilityController extends CrudController { + async createBulk(@Body() entities: CreateEmployeeAvailabilityDTO[]): Promise { return await this.commandBus.execute(new EmployeeAvailabilityBulkCreateCommand(entities)); } @@ -80,7 +82,7 @@ export class EmployeeAvailabilityController extends CrudController { + async create(@Body() entity: CreateEmployeeAvailabilityDTO): Promise { return await this.commandBus.execute(new EmployeeAvailabilityCreateCommand(entity)); } @@ -104,7 +106,7 @@ export class EmployeeAvailabilityController extends CrudController { return this.availabilityService.update(id, { ...entity }); } diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts index 6342df79e51..9872ba481b4 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.entity.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -23,11 +23,10 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @ApiProperty({ type: () => Number, description: 'Day of the week (0 = Sunday, 6 = Saturday)' }) @IsInt() @IsNotEmpty() - @MultiORMColumn({ check: 'day_of_week BETWEEN 0 AND 6' }) + @MultiORMColumn({ type: 'int', check: 'day_of_week BETWEEN 0 AND 6' }) dayOfWeek: number; @ApiProperty({ enum: AvailabilityStatusEnum }) - @IsOptional() @IsEnum(AvailabilityStatusEnum) @MultiORMColumn({ type: 'int', @@ -37,12 +36,11 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @ApiPropertyOptional({ type: () => String, - description: 'Optional notes (e.g., "Available until 2 PM")', - required: false + description: 'Optional notes (e.g., "Available until 2 PM")' }) @IsString() @IsOptional() - @MultiORMColumn({ nullable: true }) + @MultiORMColumn({ type: 'text', nullable: true }) availabilityNotes?: string; /* diff --git a/packages/core/src/lib/employee-availability/employee-availability.module.ts b/packages/core/src/lib/employee-availability/employee-availability.module.ts index 09dcd9d118b..d2959eb1509 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.module.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.module.ts @@ -1,9 +1,22 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; import { EmployeeAvailabilityService } from './employee-availability.service'; import { EmployeeAvailabilityController } from './employee-availability.controller'; +import { EmployeeAvailability } from './employee-availability.entity'; +import { TypeOrmEmployeeAvailabilityRepository } from './repository/type-orm-employee-availability.repository'; +import { MikroOrmEmployeeAvailabilityRepository } from './repository/micro-orm-employee-availability.repository'; +import { EmployeeAvailabilityBulkCreateHandler, EmployeeAvailabilityCreateHandler } from './commands'; @Module({ - providers: [EmployeeAvailabilityService], + imports: [TypeOrmModule.forFeature([EmployeeAvailability]), MikroOrmModule.forFeature([EmployeeAvailability])], + providers: [ + EmployeeAvailabilityService, + TypeOrmEmployeeAvailabilityRepository, + MikroOrmEmployeeAvailabilityRepository, + EmployeeAvailabilityBulkCreateHandler, + EmployeeAvailabilityCreateHandler + ], controllers: [EmployeeAvailabilityController] }) export class EmployeeAvailabilityModule {} diff --git a/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts b/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts index 459f3f816a2..9e0c16ee41f 100644 --- a/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts +++ b/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts @@ -1,5 +1,5 @@ import { ValueTransformer } from 'typeorm'; -import { AvailabilityStatusEnum } from '@gauzy/contracts'; +import { AvailabilityStatusEnum, AvailabilityStatusValue } from '@gauzy/contracts'; /** * Transformer to handle the conversion between the enum values @@ -16,13 +16,13 @@ export class AvailabilityStatusTransformer implements ValueTransformer { to(value: AvailabilityStatusEnum): number { switch (value) { case AvailabilityStatusEnum.Available: - return 0; + return AvailabilityStatusValue.Available; case AvailabilityStatusEnum.Partial: - return 1; + return AvailabilityStatusValue.Partial; case AvailabilityStatusEnum.Unavailable: - return 2; + return AvailabilityStatusValue.Unavailable; default: - return null; + throw new Error(`Invalid availability status: ${value}`); } } @@ -35,14 +35,14 @@ export class AvailabilityStatusTransformer implements ValueTransformer { */ from(value: number): AvailabilityStatusEnum { switch (value) { - case 0: + case AvailabilityStatusValue.Available: return AvailabilityStatusEnum.Available; - case 1: + case AvailabilityStatusValue.Partial: return AvailabilityStatusEnum.Partial; - case 2: + case AvailabilityStatusValue.Unavailable: return AvailabilityStatusEnum.Unavailable; default: - return null; + throw new Error(`Invalid status value: ${value}`); } } } From 96052b1d66b7001367106ad120c680b2c0382df2 Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Thu, 23 Jan 2025 16:54:44 +0200 Subject: [PATCH 05/16] Feedback integration --- .../lib/employee-availability/employee-availability.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts index 9872ba481b4..0fb722971c1 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.entity.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -23,7 +23,7 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @ApiProperty({ type: () => Number, description: 'Day of the week (0 = Sunday, 6 = Saturday)' }) @IsInt() @IsNotEmpty() - @MultiORMColumn({ type: 'int', check: 'day_of_week BETWEEN 0 AND 6' }) + @MultiORMColumn({ type: 'int' }) dayOfWeek: number; @ApiProperty({ enum: AvailabilityStatusEnum }) From 6c3ba5417d7766262a219ce1ff50841dae6141fc Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Thu, 23 Jan 2025 16:59:12 +0200 Subject: [PATCH 06/16] Fix deepscan --- .../employee-availability/employee-availability.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts index 8f16b2e7488..906bd885e4e 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.controller.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -2,7 +2,7 @@ import { UpdateResult } from 'typeorm'; import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put, Query, UseGuards } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ID, IEmployeeAvailability, IEmployeeAvailabilityCreateInput, IPagination } from '@gauzy/contracts'; +import { ID, IEmployeeAvailability, IPagination } from '@gauzy/contracts'; import { EmployeeAvailabilityService } from './employee-availability.service'; import { EmployeeAvailability } from './employee-availability.entity'; import { CrudController, PaginationParams } from '../core'; From 2b926a620fa5dab9425e3d468391ccd3a7eff402 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Fri, 24 Jan 2025 15:30:19 +0530 Subject: [PATCH 07/16] refactor: updated employee availability feature --- .../src/lib/employee-availability.model.ts | 54 ++++++++++--------- ...ployee-availability.bulk.create.command.ts | 2 +- .../employee-availability.create.handler.ts | 7 +++ .../commands/handlers/index.ts | 9 +++- .../employee-availability/commands/index.ts | 2 - .../dto/create-employee-availability.dto.ts | 6 ++- .../dto/update-employee-availability.dto.ts | 2 +- .../employee-availability.controller.ts | 4 +- .../employee-availability.entity.ts | 42 ++++++++++++--- .../employee-availability.module.ts | 12 +++-- .../employee-availability.service.ts | 2 +- 11 files changed, 96 insertions(+), 46 deletions(-) diff --git a/packages/contracts/src/lib/employee-availability.model.ts b/packages/contracts/src/lib/employee-availability.model.ts index be613734e07..4c6d1e266df 100644 --- a/packages/contracts/src/lib/employee-availability.model.ts +++ b/packages/contracts/src/lib/employee-availability.model.ts @@ -2,7 +2,7 @@ import { IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.mode import { IEmployee } from './employee.model'; /** - * Enum representing the availability status of an employee. + * Enum representing different availability statuses. */ export enum AvailabilityStatusEnum { Available = 'Available', @@ -10,14 +10,29 @@ export enum AvailabilityStatusEnum { Unavailable = 'Unavailable' } +/** + * Enum mapping availability statuses to numerical values. + */ export enum AvailabilityStatusValue { Available = 0, Partial = 1, Unavailable = 2 } -export interface IEmployeeAvailability extends IBasePerTenantAndOrganizationEntityModel { - employee: IEmployee; +/** + * A mapping object to relate status labels to their respective numerical values. + */ +export const AvailabilityStatusMap: Record = { + [AvailabilityStatusEnum.Available]: AvailabilityStatusValue.Available, + [AvailabilityStatusEnum.Partial]: AvailabilityStatusValue.Partial, + [AvailabilityStatusEnum.Unavailable]: AvailabilityStatusValue.Unavailable +}; + +/** + * Base interface for Employee Availability data. + * Includes common properties shared across different input types. + */ +interface IBaseEmployeeAvailability extends IBasePerTenantAndOrganizationEntityModel { employeeId: ID; startDate: Date; endDate: Date; @@ -27,35 +42,24 @@ export interface IEmployeeAvailability extends IBasePerTenantAndOrganizationEnti } /** - * Input interface for finding Employee Availability records. + * Represents an Employee Availability record. */ -export interface IEmployeeAvailabilityFindInput { - employeeId?: ID; - availabilityStatus?: AvailabilityStatusEnum; - startDate?: Date; - endDate?: Date; +export interface IEmployeeAvailability extends IBaseEmployeeAvailability { + employeeId: ID; + employee: IEmployee; } +/** + * Input interface for finding Employee Availability records. + */ +export interface IEmployeeAvailabilityFindInput extends Partial {} + /** * Input interface for creating new Employee Availability records. */ -export interface IEmployeeAvailabilityCreateInput extends IBasePerTenantAndOrganizationEntityModel { - employeeId: ID; - startDate: Date; - endDate: Date; - dayOfWeek: number; // 0 = Sunday, 6 = Saturday - availabilityStatus: AvailabilityStatusEnum; - availabilityNotes?: string; -} +export interface IEmployeeAvailabilityCreateInput extends IBaseEmployeeAvailability {} /** * Input interface for updating Employee Availability records. */ -export interface IEmployeeAvailabilityUpdateInput extends IBasePerTenantAndOrganizationEntityModel { - employeeId?: ID; - startDate?: Date; - endDate?: Date; - dayOfWeek?: number; // 0 = Sunday, 6 = Saturday - availabilityStatus?: AvailabilityStatusEnum; - availabilityNotes?: string; -} +export interface IEmployeeAvailabilityUpdateInput extends Partial {} diff --git a/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts index fed2de77fec..627e75b3975 100644 --- a/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts +++ b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts @@ -2,7 +2,7 @@ import { ICommand } from '@nestjs/cqrs'; import { IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; export class EmployeeAvailabilityBulkCreateCommand implements ICommand { - static readonly type = '[Employee Bulk Availability ] Create'; + static readonly type = '[Employee Availability Bulk] Create'; constructor(public readonly input: IEmployeeAvailabilityCreateInput[]) {} } diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts index 02c81b8cd3d..6bda9e28927 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts @@ -10,6 +10,13 @@ import { EmployeeAvailabilityCreateCommand } from '../employee-availability.crea export class EmployeeAvailabilityCreateHandler implements ICommandHandler { constructor(private readonly availabilityService: EmployeeAvailabilityService) {} + /** + * Handles the creation of an employee availability record. + * + * @param {EmployeeAvailabilityCreateCommand} command - The command containing employee availability details. + * @returns {Promise} - The newly created employee availability record. + * @throws {BadRequestException} - If any validation fails (e.g., missing fields, invalid dates). + */ public async execute(command: EmployeeAvailabilityCreateCommand): Promise { const { input } = command; const { startDate, endDate, employeeId, dayOfWeek, availabilityStatus } = input; diff --git a/packages/core/src/lib/employee-availability/commands/handlers/index.ts b/packages/core/src/lib/employee-availability/commands/handlers/index.ts index 68c458801b7..af7f9627baf 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/index.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/index.ts @@ -1,2 +1,7 @@ -export * from './employee-availability.bulk.create.handler'; -export * from './employee-availability.create.handler'; +import { EmployeeAvailabilityBulkCreateHandler } from './employee-availability.bulk.create.handler'; +import { EmployeeAvailabilityCreateHandler } from './employee-availability.create.handler'; + +/** + * Exports all command handlers for EmployeeAvailability.` + */ +export const CommandHandlers = [EmployeeAvailabilityBulkCreateHandler, EmployeeAvailabilityCreateHandler]; diff --git a/packages/core/src/lib/employee-availability/commands/index.ts b/packages/core/src/lib/employee-availability/commands/index.ts index 549814fe287..33eae03e063 100644 --- a/packages/core/src/lib/employee-availability/commands/index.ts +++ b/packages/core/src/lib/employee-availability/commands/index.ts @@ -1,4 +1,2 @@ export * from './employee-availability.bulk.create.command'; export * from './employee-availability.create.command'; -export * from './handlers/employee-availability.bulk.create.handler'; -export * from './handlers/employee-availability.create.handler'; diff --git a/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts index cbb2634496a..bc46622da00 100644 --- a/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts +++ b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts @@ -1,8 +1,12 @@ import { IsUUID, IsDateString, IsEnum, IsOptional, IsString, IsInt } from 'class-validator'; import { AvailabilityStatusEnum, ID, IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { TenantOrganizationBaseDTO } from '../../core/dto/tenant-organization-base.dto'; -export class CreateEmployeeAvailabilityDTO implements IEmployeeAvailabilityCreateInput { +export class CreateEmployeeAvailabilityDTO + extends TenantOrganizationBaseDTO + implements IEmployeeAvailabilityCreateInput +{ @ApiProperty({ type: () => String }) @IsUUID() employeeId: ID; diff --git a/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts b/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts index 8848837d6ef..5df3c0c562a 100644 --- a/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts +++ b/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts @@ -1,6 +1,6 @@ import { PartialType } from '@nestjs/mapped-types'; -import { CreateEmployeeAvailabilityDTO } from './create-employee-availability.dto'; import { IEmployeeAvailabilityUpdateInput } from '@gauzy/contracts'; +import { CreateEmployeeAvailabilityDTO } from './create-employee-availability.dto'; export class UpdateEmployeeAvailabilityDTO extends PartialType(CreateEmployeeAvailabilityDTO) diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts index 906bd885e4e..bc619fcacba 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.controller.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -6,13 +6,13 @@ import { ID, IEmployeeAvailability, IPagination } from '@gauzy/contracts'; import { EmployeeAvailabilityService } from './employee-availability.service'; import { EmployeeAvailability } from './employee-availability.entity'; import { CrudController, PaginationParams } from '../core'; -import { TenantPermissionGuard, UUIDValidationPipe } from '../shared'; +import { PermissionGuard, TenantPermissionGuard, UUIDValidationPipe } from '../shared'; import { EmployeeAvailabilityBulkCreateCommand, EmployeeAvailabilityCreateCommand } from './commands'; import { CreateEmployeeAvailabilityDTO } from './dto/create-employee-availability.dto'; import { UpdateEmployeeAvailabilityDTO } from './dto/update-employee-availability.dto'; @ApiTags('EmployeeAvailability') -@UseGuards(TenantPermissionGuard) +@UseGuards(TenantPermissionGuard, PermissionGuard) @Controller('/employee-availability') export class EmployeeAvailabilityController extends CrudController { constructor( diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts index 0fb722971c1..f41f9cf3070 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.entity.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -1,31 +1,47 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsDate, IsInt, IsOptional, IsEnum } from 'class-validator'; +import { IsNotEmpty, IsString, IsDate, IsInt, IsOptional, IsEnum, IsUUID } from 'class-validator'; import { JoinColumn, RelationId } from 'typeorm'; -import { AvailabilityStatusEnum, ID, IEmployeeAvailability } from '@gauzy/contracts'; +import { AvailabilityStatusEnum, ID, IEmployee, IEmployeeAvailability } from '@gauzy/contracts'; import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; import { MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from './../core/decorators/entity'; import { AvailabilityStatusTransformer } from '../shared/pipes/employee-availability-status.pipe'; @MultiORMEntity('employee_availability') export class EmployeeAvailability extends TenantOrganizationBaseEntity implements IEmployeeAvailability { + /** + * Represents the start date of an employee's availability period. + * This marks when the availability status takes effect. + */ @ApiProperty({ type: () => Date }) @IsDate() @IsNotEmpty() @MultiORMColumn() startDate: Date; + /** + * Represents the end date of an employee's availability period. + * This marks when the availability status expires. + */ @ApiProperty({ type: () => Date }) @IsDate() @IsNotEmpty() @MultiORMColumn() endDate: Date; + /** + * The day of the week corresponding to the availability. + * Values range from `0` (Sunday) to `6` (Saturday). + */ @ApiProperty({ type: () => Number, description: 'Day of the week (0 = Sunday, 6 = Saturday)' }) @IsInt() @IsNotEmpty() - @MultiORMColumn({ type: 'int' }) + @MultiORMColumn() dayOfWeek: number; + /** + * The availability status of the employee. + * Uses `AvailabilityStatusEnum` to define the available states. + */ @ApiProperty({ enum: AvailabilityStatusEnum }) @IsEnum(AvailabilityStatusEnum) @MultiORMColumn({ @@ -34,6 +50,10 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement }) availabilityStatus: AvailabilityStatusEnum; + /** + * Optional notes providing additional details about the availability. + * Example: "Available until 2 PM" or "Remote work only." + */ @ApiPropertyOptional({ type: () => String, description: 'Optional notes (e.g., "Available until 2 PM")' @@ -50,16 +70,24 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement */ /** - * Employee + * Reference to the Employee associated with this availability record. + * Establishes a many-to-one relationship, meaning multiple availability records + * can be linked to a single employee. */ - @MultiORMManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, (it) => it.availabilities, { + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @JoinColumn() - employee: Employee; + employee: IEmployee; + /** + * The UUID representing the linked `Employee` record. + * This serves as the foreign key that connects the availability record to a specific employee. + */ @ApiProperty({ type: () => String }) + @IsUUID() @RelationId((it: EmployeeAvailability) => it.employee) - @MultiORMColumn() + @MultiORMColumn({ relationId: true }) employeeId: ID; } diff --git a/packages/core/src/lib/employee-availability/employee-availability.module.ts b/packages/core/src/lib/employee-availability/employee-availability.module.ts index d2959eb1509..ef1ec3c59c9 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.module.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.module.ts @@ -1,21 +1,25 @@ import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { CommandHandlers } from './commands/handlers'; import { EmployeeAvailabilityService } from './employee-availability.service'; import { EmployeeAvailabilityController } from './employee-availability.controller'; import { EmployeeAvailability } from './employee-availability.entity'; import { TypeOrmEmployeeAvailabilityRepository } from './repository/type-orm-employee-availability.repository'; import { MikroOrmEmployeeAvailabilityRepository } from './repository/micro-orm-employee-availability.repository'; -import { EmployeeAvailabilityBulkCreateHandler, EmployeeAvailabilityCreateHandler } from './commands'; @Module({ - imports: [TypeOrmModule.forFeature([EmployeeAvailability]), MikroOrmModule.forFeature([EmployeeAvailability])], + imports: [ + TypeOrmModule.forFeature([EmployeeAvailability]), + MikroOrmModule.forFeature([EmployeeAvailability]), + CqrsModule + ], providers: [ EmployeeAvailabilityService, TypeOrmEmployeeAvailabilityRepository, MikroOrmEmployeeAvailabilityRepository, - EmployeeAvailabilityBulkCreateHandler, - EmployeeAvailabilityCreateHandler + ...CommandHandlers ], controllers: [EmployeeAvailabilityController] }) diff --git a/packages/core/src/lib/employee-availability/employee-availability.service.ts b/packages/core/src/lib/employee-availability/employee-availability.service.ts index 4901b98b7f8..cb0e152f49d 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.service.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; +import { TenantAwareCrudService } from './../core/crud'; import { TypeOrmEmployeeAvailabilityRepository } from './repository/type-orm-employee-availability.repository'; import { MikroOrmEmployeeAvailabilityRepository } from './repository/micro-orm-employee-availability.repository'; -import { TenantAwareCrudService } from './../core/crud'; import { EmployeeAvailability } from './employee-availability.entity'; @Injectable() From 55092e816a3a1fa84ab7bf4c292e896bf1328273 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Fri, 24 Jan 2025 15:40:31 +0530 Subject: [PATCH 08/16] fix: entity metadata for Employee#availabilities was not found --- packages/core/src/lib/core/entities/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/lib/core/entities/index.ts b/packages/core/src/lib/core/entities/index.ts index 6d796401cb6..8c6bda7261a 100644 --- a/packages/core/src/lib/core/entities/index.ts +++ b/packages/core/src/lib/core/entities/index.ts @@ -32,6 +32,7 @@ import { EmailTemplate, Employee, EmployeeAppointment, + EmployeeAvailability, EmployeeAward, EmployeeLevel, EmployeePhone, @@ -190,6 +191,7 @@ export const coreEntities = [ EmailTemplate, Employee, EmployeeAppointment, + EmployeeAvailability, EmployeeAward, EmployeeLevel, EmployeePhone, From a6a6e4c7b6656af82492cb2f9dcc1c067fbfa0ff Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Fri, 24 Jan 2025 13:51:13 +0200 Subject: [PATCH 09/16] Integration of requested changes --- .../src/lib/employee-availability.model.ts | 1 - ...ployee-availability.bulk.create.handler.ts | 21 +++----- .../dto/create-employee-availability.dto.ts | 50 ++++++------------- .../dto/update-employee-availability.dto.ts | 3 +- .../employee-availability.entity.ts | 10 +++- .../employee-availability-status.pipe.ts | 0 6 files changed, 33 insertions(+), 52 deletions(-) rename packages/core/src/lib/{shared => employee-availability}/pipes/employee-availability-status.pipe.ts (100%) diff --git a/packages/contracts/src/lib/employee-availability.model.ts b/packages/contracts/src/lib/employee-availability.model.ts index 4c6d1e266df..9520e78dcae 100644 --- a/packages/contracts/src/lib/employee-availability.model.ts +++ b/packages/contracts/src/lib/employee-availability.model.ts @@ -45,7 +45,6 @@ interface IBaseEmployeeAvailability extends IBasePerTenantAndOrganizationEntityM * Represents an Employee Availability record. */ export interface IEmployeeAvailability extends IBaseEmployeeAvailability { - employeeId: ID; employee: IEmployee; } diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts index fb13f8f92e7..1d7c606ad49 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts @@ -19,20 +19,13 @@ export class EmployeeAvailabilityBulkCreateHandler implements ICommandHandler { - const availability = new EmployeeAvailability({ - ...item, - tenantId - }); - return this.commandBus.execute(new EmployeeAvailabilityCreateCommand(availability)); - }) - ); - allAvailability.push(...results); - } catch (error) { - throw new BadRequestException('Failed to create some availability records: ' + error.message); + for (const item of input) { + let availability = new EmployeeAvailability({ + ...item, + tenantId + }); + availability = await this.commandBus.execute(new EmployeeAvailabilityCreateCommand(availability)); + allAvailability.push(availability); } return allAvailability; } diff --git a/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts index bc46622da00..fa1d69a31cf 100644 --- a/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts +++ b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts @@ -1,36 +1,18 @@ -import { IsUUID, IsDateString, IsEnum, IsOptional, IsString, IsInt } from 'class-validator'; -import { AvailabilityStatusEnum, ID, IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { TenantOrganizationBaseDTO } from '../../core/dto/tenant-organization-base.dto'; +import { IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; +import { EmployeeAvailability } from '../employee-availability.entity'; +import { TenantOrganizationBaseDTO } from '../../core'; +import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; export class CreateEmployeeAvailabilityDTO - extends TenantOrganizationBaseDTO - implements IEmployeeAvailabilityCreateInput -{ - @ApiProperty({ type: () => String }) - @IsUUID() - employeeId: ID; - - @ApiProperty({ type: () => Date }) - @IsDateString() - startDate: Date; - - @ApiProperty({ type: () => Date }) - @IsDateString() - endDate: Date; - - @ApiProperty({ type: () => Number }) - @IsInt() - dayOfWeek: number; - - @ApiProperty({ enum: AvailabilityStatusEnum }) - @IsEnum(AvailabilityStatusEnum) - availabilityStatus: AvailabilityStatusEnum; - - @ApiPropertyOptional({ - type: () => String - }) - @IsOptional() - @IsString() - availabilityNotes?: string; -} + extends IntersectionType( + PartialType(TenantOrganizationBaseDTO), + PickType(EmployeeAvailability, [ + 'dayOfWeek', + 'startDate', + 'endDate', + 'availabilityNotes', + 'availabilityStatus', + 'employeeId' + ]) + ) + implements IEmployeeAvailabilityCreateInput {} diff --git a/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts b/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts index 5df3c0c562a..8c50bd31e3c 100644 --- a/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts +++ b/packages/core/src/lib/employee-availability/dto/update-employee-availability.dto.ts @@ -1,7 +1,8 @@ import { PartialType } from '@nestjs/mapped-types'; import { IEmployeeAvailabilityUpdateInput } from '@gauzy/contracts'; import { CreateEmployeeAvailabilityDTO } from './create-employee-availability.dto'; +import { IntersectionType } from '@nestjs/swagger'; export class UpdateEmployeeAvailabilityDTO - extends PartialType(CreateEmployeeAvailabilityDTO) + extends IntersectionType(PartialType(CreateEmployeeAvailabilityDTO)) implements IEmployeeAvailabilityUpdateInput {} diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts index f41f9cf3070..85cfb38913c 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.entity.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -1,10 +1,11 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsDate, IsInt, IsOptional, IsEnum, IsUUID } from 'class-validator'; +import { IsNotEmpty, IsString, IsDate, IsInt, IsOptional, IsEnum, IsUUID, Min, Max } from 'class-validator'; import { JoinColumn, RelationId } from 'typeorm'; import { AvailabilityStatusEnum, ID, IEmployee, IEmployeeAvailability } from '@gauzy/contracts'; import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; import { MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from './../core/decorators/entity'; -import { AvailabilityStatusTransformer } from '../shared/pipes/employee-availability-status.pipe'; +import { AvailabilityStatusTransformer } from './pipes/employee-availability-status.pipe'; +import { IsBeforeDate } from '../shared/validators'; @MultiORMEntity('employee_availability') export class EmployeeAvailability extends TenantOrganizationBaseEntity implements IEmployeeAvailability { @@ -15,6 +16,9 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @ApiProperty({ type: () => Date }) @IsDate() @IsNotEmpty() + @IsBeforeDate(EmployeeAvailability, (it) => it.endDate, { + message: 'Start date must be before to the end date' + }) @MultiORMColumn() startDate: Date; @@ -35,6 +39,8 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement @ApiProperty({ type: () => Number, description: 'Day of the week (0 = Sunday, 6 = Saturday)' }) @IsInt() @IsNotEmpty() + @Min(0, { message: 'Day of week must be between 0 and 6' }) + @Max(6, { message: 'Day of week must be between 0 and 6' }) @MultiORMColumn() dayOfWeek: number; diff --git a/packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts b/packages/core/src/lib/employee-availability/pipes/employee-availability-status.pipe.ts similarity index 100% rename from packages/core/src/lib/shared/pipes/employee-availability-status.pipe.ts rename to packages/core/src/lib/employee-availability/pipes/employee-availability-status.pipe.ts From 39e500f3886e74a1d08623d35faf48d71ece8055 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE Date: Sun, 26 Jan 2025 15:41:30 +0530 Subject: [PATCH 10/16] fix: #5293 bulk insert method for employee availability --- .../core/src/lib/core/crud/crud.service.ts | 82 +++++++++++++++++-- .../core/crud/tenant-aware-crud.service.ts | 6 +- ...ployee-availability.bulk.create.command.ts | 2 +- ...ployee-availability.bulk.create.handler.ts | 36 ++++---- .../employee-availability.create.handler.ts | 29 +------ .../employee-availability.entity.ts | 7 +- .../employee-availability.service.ts | 6 +- 7 files changed, 109 insertions(+), 59 deletions(-) diff --git a/packages/core/src/lib/core/crud/crud.service.ts b/packages/core/src/lib/core/crud/crud.service.ts index a9d6395ac14..75eda5adc5f 100644 --- a/packages/core/src/lib/core/crud/crud.service.ts +++ b/packages/core/src/lib/core/crud/crud.service.ts @@ -16,7 +16,7 @@ import { import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { Collection, CreateOptions, FilterQuery as MikroFilterQuery, RequiredEntityData, wrap } from '@mikro-orm/core'; import { AssignOptions } from '@mikro-orm/knex'; -import { IPagination } from '@gauzy/contracts'; +import { ID, IPagination } from '@gauzy/contracts'; import { BaseEntity, SoftDeletableBaseEntity } from '../entities/internal'; import { multiORMCreateQueryBuilder } from '../../core/orm/query-builder/query-builder.factory'; import { IQueryBuilder } from '../../core/orm/query-builder/iquery-builder'; @@ -45,7 +45,6 @@ import { ITryRequest } from './try-request'; const ormType: MultiORM = getORMType(); export abstract class CrudService implements ICrudService { - constructor( protected readonly typeOrmRepository: Repository, protected readonly mikroOrmRepository: MikroOrmBaseEntityRepository @@ -347,7 +346,7 @@ export abstract class CrudService implements ICrudService< * @param options * @returns */ - public async findOneByIdString(id: T['id'], options?: IFindOneOptions): Promise { + public async findOneByIdString(id: ID, options?: IFindOneOptions): Promise { let record: T; switch (this.ormType) { @@ -434,6 +433,79 @@ export abstract class CrudService implements ICrudService< return this.serialize(record); } + /** + * Bulk creates new entities or updates existing ones based on the provided entity data. + * + * @param entities The array of partial entity data for creation or update. + * @param createOptions Options for the creation of the entity in MikroORM. + * @param assignOptions Options for assigning existing entities in MikroORM. + * @returns The created or updated entities. + */ + public async bulkCreate( + partialEntities: IPartialEntity[], + createOptions: CreateOptions = { + partial: true, + managed: true + }, + assignOptions: AssignOptions = { + updateNestedEntities: false, + onlyOwnProperties: true + } + ): Promise { + try { + switch (this.ormType) { + case MultiORMEnum.MikroORM: + try { + const newEntities: T[] = []; + + for (const partialEntity of partialEntities) { + if (partialEntity['id']) { + // Try to find the existing entity + const existingEntity = await this.mikroOrmRepository.findOne(partialEntity['id']); + if (existingEntity) { + // If found, perform an update (upsert) + this.mikroOrmRepository.assign(existingEntity, partialEntity as any, assignOptions); + newEntities.push(existingEntity); + continue; + } + } + + // Create a new entity using MikroORM + const newEntity = this.mikroOrmRepository.create( + partialEntity as RequiredEntityData, + createOptions + ); + newEntities.push(newEntity); + } + + // Persist all new entities and flush + await this.mikroOrmRepository.persistAndFlush(newEntities); + return newEntities.map(entity => this.serialize(entity)); + } catch (error) { + console.error('Error during MikroORM bulk create transaction:', error); + } + break; + + case MultiORMEnum.TypeORM: + try { + // Bulk insert using TypeORM's `save` method for better performance + const newEntities = this.typeOrmRepository.create(partialEntities as DeepPartial[]); + return await this.typeOrmRepository.save(newEntities); + } catch (error) { + console.error('Error during TypeORM bulk create transaction:', error); + } + break; + + default: + throw new Error(`Not implemented for ${this.ormType}`); + } + } catch (error) { + console.error('Error in CRUD service bulk create method:', error); + throw new BadRequestException(error); + } + } + + /** * Creates a new entity or updates an existing one based on the provided entity data. * @@ -645,7 +717,7 @@ export abstract class CrudService implements ICrudService< * @param saveOptions - Additional save options for the ORM operation (specific to TypeORM). * @returns A promise that resolves to the softly removed entity. */ - public async softRemove(id: T['id'], options?: IFindOneOptions, saveOptions?: SaveOptions): Promise { + public async softRemove(id: ID, options?: IFindOneOptions, saveOptions?: SaveOptions): Promise { try { switch (this.ormType) { case MultiORMEnum.MikroORM: { @@ -681,7 +753,7 @@ export abstract class CrudService implements ICrudService< * @param options - Optional settings for database save operations. * @returns A promise that resolves with the recovered entity. */ - public async softRecover(id: T['id'], options?: IFindOneOptions, saveOptions?: SaveOptions): Promise { + public async softRecover(id: ID, options?: IFindOneOptions, saveOptions?: SaveOptions): Promise { try { switch (this.ormType) { case MultiORMEnum.MikroORM: { diff --git a/packages/core/src/lib/core/crud/tenant-aware-crud.service.ts b/packages/core/src/lib/core/crud/tenant-aware-crud.service.ts index 2154948e7bc..d125ed3ef85 100644 --- a/packages/core/src/lib/core/crud/tenant-aware-crud.service.ts +++ b/packages/core/src/lib/core/crud/tenant-aware-crud.service.ts @@ -1,7 +1,7 @@ import { NotFoundException } from '@nestjs/common'; import { DeleteResult, FindOptionsWhere, FindManyOptions, FindOneOptions, Repository, UpdateResult } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import { IPagination, IUser, PermissionsEnum } from '@gauzy/contracts'; +import { ID, IPagination, IUser, PermissionsEnum } from '@gauzy/contracts'; import { isNotEmpty } from '@gauzy/utils'; import { MikroOrmBaseEntityRepository } from '../../core/repository/mikro-orm-base-entity.repository'; import { RequestContext } from '../context'; @@ -239,7 +239,7 @@ export abstract class TenantAwareCrudService * @param options * @returns */ - public async findOneOrFailByIdString(id: T['id'], options?: FindOneOptions): Promise> { + public async findOneOrFailByIdString(id: ID, options?: FindOneOptions): Promise> { return await super.findOneOrFailByIdString(id, this.findOneWithTenant(options)); } @@ -282,7 +282,7 @@ export abstract class TenantAwareCrudService * @param options * @returns */ - public async findOneByIdString(id: T['id'], options?: FindOneOptions): Promise { + public async findOneByIdString(id: ID, options?: FindOneOptions): Promise { return await super.findOneByIdString(id, this.findOneWithTenant(options)); } diff --git a/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts index 627e75b3975..fbe674883d0 100644 --- a/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts +++ b/packages/core/src/lib/employee-availability/commands/employee-availability.bulk.create.command.ts @@ -2,7 +2,7 @@ import { ICommand } from '@nestjs/cqrs'; import { IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; export class EmployeeAvailabilityBulkCreateCommand implements ICommand { - static readonly type = '[Employee Availability Bulk] Create'; + static readonly type = '[Employee Availability] Bulk Create'; constructor(public readonly input: IEmployeeAvailabilityCreateInput[]) {} } diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts index 1d7c606ad49..5e627b5bcf3 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts @@ -1,32 +1,36 @@ -import { BadRequestException } from '@nestjs/common'; -import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { IEmployeeAvailability } from '@gauzy/contracts'; import { RequestContext } from '../../../core/context'; +import { EmployeeAvailabilityService } from '../../employee-availability.service'; import { EmployeeAvailabilityBulkCreateCommand } from '../employee-availability.bulk.create.command'; import { EmployeeAvailability } from '../../employee-availability.entity'; -import { EmployeeAvailabilityCreateCommand } from '../employee-availability.create.command'; +/** + * Handles the bulk creation of employee availability records. + */ @CommandHandler(EmployeeAvailabilityBulkCreateCommand) export class EmployeeAvailabilityBulkCreateHandler implements ICommandHandler { - constructor(private readonly commandBus: CommandBus) {} + constructor(private readonly _availabilityService: EmployeeAvailabilityService) {} + /** + * Executes the bulk creation command for employee availability. + * + * @param command The command containing the list of availability records to create. + * @returns A promise resolving to the list of created employee availability records. + */ public async execute(command: EmployeeAvailabilityBulkCreateCommand): Promise { const { input } = command; - if (!Array.isArray(input) || input.length === 0) { - throw new BadRequestException('Input must be a non-empty array of availability records.'); - } - - const allAvailability: IEmployeeAvailability[] = []; const tenantId = RequestContext.currentTenantId(); - for (const item of input) { - let availability = new EmployeeAvailability({ + // Prepare employee availability records with tenantId + const employeeAvailabilities = input.map(item => + new EmployeeAvailability({ ...item, tenantId - }); - availability = await this.commandBus.execute(new EmployeeAvailabilityCreateCommand(availability)); - allAvailability.push(availability); - } - return allAvailability; + }) + ); + + // Perform bulk insert using the availability service + return await this._availabilityService.bulkCreate(employeeAvailabilities); } } diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts index 6bda9e28927..6a958bb9f83 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts @@ -1,4 +1,3 @@ -import { BadRequestException } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { IEmployeeAvailability } from '@gauzy/contracts'; import { RequestContext } from '../../../core/context'; @@ -8,7 +7,7 @@ import { EmployeeAvailabilityCreateCommand } from '../employee-availability.crea @CommandHandler(EmployeeAvailabilityCreateCommand) export class EmployeeAvailabilityCreateHandler implements ICommandHandler { - constructor(private readonly availabilityService: EmployeeAvailabilityService) {} + constructor(private readonly _availabilityService: EmployeeAvailabilityService) {} /** * Handles the creation of an employee availability record. @@ -19,33 +18,11 @@ export class EmployeeAvailabilityCreateHandler implements ICommandHandler { const { input } = command; - const { startDate, endDate, employeeId, dayOfWeek, availabilityStatus } = input; - - if (!employeeId) { - throw new BadRequestException('Employee ID is required.'); - } - if (typeof dayOfWeek !== 'number' || dayOfWeek < 0 || dayOfWeek > 6) { - throw new BadRequestException('Day of week must be a number between 0 and 6.'); - } - if (!availabilityStatus) { - throw new BadRequestException('Availability status is required.'); - } - - if (!startDate || !endDate) { - throw new BadRequestException('Start date and end date are required.'); - } - - if (new Date(endDate) <= new Date(startDate)) { - throw new BadRequestException('End date must be after start date.'); - } - const tenantId = RequestContext.currentTenantId(); - const availability = new EmployeeAvailability({ + return await this._availabilityService.create(new EmployeeAvailability({ ...input, tenantId - }); - - return await this.availabilityService.create(availability); + })); } } diff --git a/packages/core/src/lib/employee-availability/employee-availability.entity.ts b/packages/core/src/lib/employee-availability/employee-availability.entity.ts index 85cfb38913c..37ed2ffb31c 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.entity.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.entity.ts @@ -37,8 +37,8 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement * Values range from `0` (Sunday) to `6` (Saturday). */ @ApiProperty({ type: () => Number, description: 'Day of the week (0 = Sunday, 6 = Saturday)' }) - @IsInt() @IsNotEmpty() + @IsInt() @Min(0, { message: 'Day of week must be between 0 and 6' }) @Max(6, { message: 'Day of week must be between 0 and 6' }) @MultiORMColumn() @@ -50,10 +50,7 @@ export class EmployeeAvailability extends TenantOrganizationBaseEntity implement */ @ApiProperty({ enum: AvailabilityStatusEnum }) @IsEnum(AvailabilityStatusEnum) - @MultiORMColumn({ - type: 'int', - transformer: new AvailabilityStatusTransformer() - }) + @MultiORMColumn({ type: 'int', transformer: new AvailabilityStatusTransformer() }) availabilityStatus: AvailabilityStatusEnum; /** diff --git a/packages/core/src/lib/employee-availability/employee-availability.service.ts b/packages/core/src/lib/employee-availability/employee-availability.service.ts index cb0e152f49d..fb2b0d61f0d 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.service.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { TenantAwareCrudService } from './../core/crud'; +import { TenantAwareCrudService } from './../core/crud/tenant-aware-crud.service'; import { TypeOrmEmployeeAvailabilityRepository } from './repository/type-orm-employee-availability.repository'; import { MikroOrmEmployeeAvailabilityRepository } from './repository/micro-orm-employee-availability.repository'; import { EmployeeAvailability } from './employee-availability.entity'; @@ -7,8 +7,8 @@ import { EmployeeAvailability } from './employee-availability.entity'; @Injectable() export class EmployeeAvailabilityService extends TenantAwareCrudService { constructor( - typeOrmEmployeeAvailabilityRepository: TypeOrmEmployeeAvailabilityRepository, - mikroOrmEmployeeAvailabilityRepository: MikroOrmEmployeeAvailabilityRepository + readonly typeOrmEmployeeAvailabilityRepository: TypeOrmEmployeeAvailabilityRepository, + readonly mikroOrmEmployeeAvailabilityRepository: MikroOrmEmployeeAvailabilityRepository ) { super(typeOrmEmployeeAvailabilityRepository, mikroOrmEmployeeAvailabilityRepository); } From 9037f32dafc709ffcd38d3dd28ced821c2a18377 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE Date: Sun, 26 Jan 2025 16:35:45 +0530 Subject: [PATCH 11/16] fix: #5293 bulk insert method for employee availability --- .../core/src/lib/core/crud/crud.service.ts | 73 ------------------- ...ployee-availability.bulk.create.handler.ts | 2 +- .../employee-availability.create.handler.ts | 2 +- .../employee-availability.service.ts | 67 ++++++++++++++++- 4 files changed, 68 insertions(+), 76 deletions(-) diff --git a/packages/core/src/lib/core/crud/crud.service.ts b/packages/core/src/lib/core/crud/crud.service.ts index 75eda5adc5f..bd5207d6ed8 100644 --- a/packages/core/src/lib/core/crud/crud.service.ts +++ b/packages/core/src/lib/core/crud/crud.service.ts @@ -433,79 +433,6 @@ export abstract class CrudService implements ICrudService< return this.serialize(record); } - /** - * Bulk creates new entities or updates existing ones based on the provided entity data. - * - * @param entities The array of partial entity data for creation or update. - * @param createOptions Options for the creation of the entity in MikroORM. - * @param assignOptions Options for assigning existing entities in MikroORM. - * @returns The created or updated entities. - */ - public async bulkCreate( - partialEntities: IPartialEntity[], - createOptions: CreateOptions = { - partial: true, - managed: true - }, - assignOptions: AssignOptions = { - updateNestedEntities: false, - onlyOwnProperties: true - } - ): Promise { - try { - switch (this.ormType) { - case MultiORMEnum.MikroORM: - try { - const newEntities: T[] = []; - - for (const partialEntity of partialEntities) { - if (partialEntity['id']) { - // Try to find the existing entity - const existingEntity = await this.mikroOrmRepository.findOne(partialEntity['id']); - if (existingEntity) { - // If found, perform an update (upsert) - this.mikroOrmRepository.assign(existingEntity, partialEntity as any, assignOptions); - newEntities.push(existingEntity); - continue; - } - } - - // Create a new entity using MikroORM - const newEntity = this.mikroOrmRepository.create( - partialEntity as RequiredEntityData, - createOptions - ); - newEntities.push(newEntity); - } - - // Persist all new entities and flush - await this.mikroOrmRepository.persistAndFlush(newEntities); - return newEntities.map(entity => this.serialize(entity)); - } catch (error) { - console.error('Error during MikroORM bulk create transaction:', error); - } - break; - - case MultiORMEnum.TypeORM: - try { - // Bulk insert using TypeORM's `save` method for better performance - const newEntities = this.typeOrmRepository.create(partialEntities as DeepPartial[]); - return await this.typeOrmRepository.save(newEntities); - } catch (error) { - console.error('Error during TypeORM bulk create transaction:', error); - } - break; - - default: - throw new Error(`Not implemented for ${this.ormType}`); - } - } catch (error) { - console.error('Error in CRUD service bulk create method:', error); - throw new BadRequestException(error); - } - } - - /** * Creates a new entity or updates an existing one based on the provided entity data. * diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts index 5e627b5bcf3..8fbdf4b0475 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts @@ -20,7 +20,7 @@ export class EmployeeAvailabilityBulkCreateHandler implements ICommandHandler { const { input } = command; - const tenantId = RequestContext.currentTenantId(); + const tenantId = RequestContext.currentTenantId() ?? input.tenantId; // Prepare employee availability records with tenantId const employeeAvailabilities = input.map(item => diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts index 6a958bb9f83..e0b4412b905 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.create.handler.ts @@ -18,7 +18,7 @@ export class EmployeeAvailabilityCreateHandler implements ICommandHandler { const { input } = command; - const tenantId = RequestContext.currentTenantId(); + const tenantId = RequestContext.currentTenantId() ?? input.tenantId; return await this._availabilityService.create(new EmployeeAvailability({ ...input, diff --git a/packages/core/src/lib/employee-availability/employee-availability.service.ts b/packages/core/src/lib/employee-availability/employee-availability.service.ts index fb2b0d61f0d..a2b08071249 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.service.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.service.ts @@ -1,5 +1,9 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { DeepPartial } from 'typeorm'; +import { IEmployeeAvailability } from '@gauzy/contracts'; +import { RequestContext } from '../core/context/request-context'; import { TenantAwareCrudService } from './../core/crud/tenant-aware-crud.service'; +import { MultiORMEnum } from '../core/utils'; import { TypeOrmEmployeeAvailabilityRepository } from './repository/type-orm-employee-availability.repository'; import { MikroOrmEmployeeAvailabilityRepository } from './repository/micro-orm-employee-availability.repository'; import { EmployeeAvailability } from './employee-availability.entity'; @@ -12,4 +16,65 @@ export class EmployeeAvailabilityService extends TenantAwareCrudService List of created employee availability records. + */ + public async bulkCreate(entities: Partial[]): Promise { + const tenantId = RequestContext.currentTenantId(); + + // Prepare entities ensuring `tenantId` is assigned + const items = entities.map(entity => + new EmployeeAvailability({ + ...entity, + tenantId + }) + ); + + try { + switch (this.ormType) { + case MultiORMEnum.MikroORM: + try { + // Convert input entities to MikroORM format + const mikroEntities = items.map((item: EmployeeAvailability) => + this.mikroOrmRepository.create(item, { + managed: true + }) + ); + + // Bulk insert using MikroORM + await this.mikroOrmRepository.persistAndFlush(mikroEntities); + return mikroEntities; + } catch (error) { + throw new HttpException( + `Error during MikroORM bulk create transaction : ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + case MultiORMEnum.TypeORM: + try { + // Bulk insert using TypeORM's `save` method for optimized inserts + return await this.typeOrmRepository.save(items as DeepPartial[]); + } catch (error) { + throw new HttpException( + `Error during TypeORM bulk create transaction : ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + default: + throw new Error(`Not implemented for ${this.ormType}`); + } + } catch (error) { + throw new HttpException( + `Error in bulkCreate method of employee availability: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } } From 413bf4ec952f9e25920b7e9b6f7f6b870b70b3c9 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE Date: Sun, 26 Jan 2025 16:48:31 +0530 Subject: [PATCH 12/16] fix: #5293 apply validation and permissions guard --- .../dto/create-employee-availability.dto.ts | 6 +++--- .../employee-availability.controller.ts | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts index fa1d69a31cf..5f92c995eed 100644 --- a/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts +++ b/packages/core/src/lib/employee-availability/dto/create-employee-availability.dto.ts @@ -1,11 +1,11 @@ +import { IntersectionType, PickType } from '@nestjs/swagger'; import { IEmployeeAvailabilityCreateInput } from '@gauzy/contracts'; +import { TenantOrganizationBaseDTO } from '../../core/dto'; import { EmployeeAvailability } from '../employee-availability.entity'; -import { TenantOrganizationBaseDTO } from '../../core'; -import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; export class CreateEmployeeAvailabilityDTO extends IntersectionType( - PartialType(TenantOrganizationBaseDTO), + TenantOrganizationBaseDTO, PickType(EmployeeAvailability, [ 'dayOfWeek', 'startDate', diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts index bc619fcacba..d583fcfc865 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.controller.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -3,10 +3,12 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put, Query, U import { CommandBus } from '@nestjs/cqrs'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ID, IEmployeeAvailability, IPagination } from '@gauzy/contracts'; +import { CrudController, PaginationParams } from '../core/crud'; +import { Permissions } from '../shared/decorators'; +import { PermissionGuard, TenantPermissionGuard } from '../shared/guards'; +import { UseValidationPipe, UUIDValidationPipe } from '../shared/pipes'; import { EmployeeAvailabilityService } from './employee-availability.service'; import { EmployeeAvailability } from './employee-availability.entity'; -import { CrudController, PaginationParams } from '../core'; -import { PermissionGuard, TenantPermissionGuard, UUIDValidationPipe } from '../shared'; import { EmployeeAvailabilityBulkCreateCommand, EmployeeAvailabilityCreateCommand } from './commands'; import { CreateEmployeeAvailabilityDTO } from './dto/create-employee-availability.dto'; import { UpdateEmployeeAvailabilityDTO } from './dto/update-employee-availability.dto'; @@ -38,7 +40,9 @@ export class EmployeeAvailabilityController extends CrudController { return await this.commandBus.execute(new EmployeeAvailabilityBulkCreateCommand(entities)); } @@ -58,7 +62,9 @@ export class EmployeeAvailabilityController extends CrudController ): Promise> { @@ -81,9 +87,11 @@ export class EmployeeAvailabilityController extends CrudController { - return await this.commandBus.execute(new EmployeeAvailabilityCreateCommand(entity)); + return this.commandBus.execute(new EmployeeAvailabilityCreateCommand(entity)); } /** @@ -103,7 +111,9 @@ export class EmployeeAvailabilityController extends CrudController Date: Sun, 26 Jan 2025 16:49:37 +0530 Subject: [PATCH 13/16] fix: property 'tenantId' does not exist on type 'IEmployeeAvailabilityCreateInput[]' --- .../handlers/employee-availability.bulk.create.handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts index 8fbdf4b0475..5e627b5bcf3 100644 --- a/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts +++ b/packages/core/src/lib/employee-availability/commands/handlers/employee-availability.bulk.create.handler.ts @@ -20,7 +20,7 @@ export class EmployeeAvailabilityBulkCreateHandler implements ICommandHandler { const { input } = command; - const tenantId = RequestContext.currentTenantId() ?? input.tenantId; + const tenantId = RequestContext.currentTenantId(); // Prepare employee availability records with tenantId const employeeAvailabilities = input.map(item => From fdab334951e4fc28b0d2d4ef8e6bf6e74741b1e5 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE Date: Sun, 26 Jan 2025 22:34:16 +0530 Subject: [PATCH 14/16] feat: #5293 added permission for Employee Availability --- .../src/lib/role-permission.model.ts | 15 ++++++++++--- .../employee-availability.controller.ts | 21 ++++++++++--------- .../default-role-permissions.ts | 21 ++++++++++++++++--- packages/ui-core/i18n/assets/i18n/bg.json | 6 +++++- packages/ui-core/i18n/assets/i18n/de.json | 6 +++++- packages/ui-core/i18n/assets/i18n/en.json | 6 +++++- packages/ui-core/i18n/assets/i18n/es.json | 6 +++++- packages/ui-core/i18n/assets/i18n/fr.json | 6 +++++- packages/ui-core/i18n/assets/i18n/he.json | 6 +++++- packages/ui-core/i18n/assets/i18n/it.json | 6 +++++- packages/ui-core/i18n/assets/i18n/nl.json | 6 +++++- packages/ui-core/i18n/assets/i18n/pl.json | 6 +++++- packages/ui-core/i18n/assets/i18n/pt.json | 6 +++++- packages/ui-core/i18n/assets/i18n/ru.json | 6 +++++- packages/ui-core/i18n/assets/i18n/zh.json | 6 +++++- 15 files changed, 101 insertions(+), 28 deletions(-) diff --git a/packages/contracts/src/lib/role-permission.model.ts b/packages/contracts/src/lib/role-permission.model.ts index 727f5b75169..764d1215824 100644 --- a/packages/contracts/src/lib/role-permission.model.ts +++ b/packages/contracts/src/lib/role-permission.model.ts @@ -224,11 +224,15 @@ export enum PermissionsEnum { DASHBOARD_READ = 'DASHBOARD_READ', DASHBOARD_UPDATE = 'DASHBOARD_UPDATE', DASHBOARD_DELETE = 'DASHBOARD_DELETE', - /** Tenant API Key */ TENANT_API_KEY_CREATE = 'TENANT_API_KEY_CREATE', TENANT_API_KEY_VIEW = 'TENANT_API_KEY_VIEW', - TENANT_API_KEY_DELETE = 'TENANT_API_KEY_DELETE' + TENANT_API_KEY_DELETE = 'TENANT_API_KEY_DELETE', + /** Employee Availability CRUD Permissions Start */ + EMPLOYEE_AVAILABILITY_CREATE = 'EMPLOYEE_AVAILABILITY_CREATE', + EMPLOYEE_AVAILABILITY_READ = 'EMPLOYEE_AVAILABILITY_READ', + EMPLOYEE_AVAILABILITY_UPDATE = 'EMPLOYEE_AVAILABILITY_UPDATE', + EMPLOYEE_AVAILABILITY_DELETE = 'EMPLOYEE_AVAILABILITY_DELETE' } export const PermissionGroups = { @@ -401,7 +405,12 @@ export const PermissionGroups = { PermissionsEnum.ALLOW_MANUAL_TIME, PermissionsEnum.DELETE_SCREENSHOTS, PermissionsEnum.ACCESS_DELETE_ACCOUNT, - PermissionsEnum.ORG_MEMBER_LAST_LOG_VIEW + PermissionsEnum.ORG_MEMBER_LAST_LOG_VIEW, + /** Employee Availability CRUD Permissions Start */ + PermissionsEnum.EMPLOYEE_AVAILABILITY_CREATE, + PermissionsEnum.EMPLOYEE_AVAILABILITY_READ, + PermissionsEnum.EMPLOYEE_AVAILABILITY_UPDATE, + PermissionsEnum.EMPLOYEE_AVAILABILITY_DELETE ], //Readonly permissions, are only enabled for Super Admin/Admin role diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts index d583fcfc865..6b2ed052381 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.controller.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -2,19 +2,20 @@ import { UpdateResult } from 'typeorm'; import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put, Query, UseGuards } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ID, IEmployeeAvailability, IPagination } from '@gauzy/contracts'; +import { ID, IEmployeeAvailability, IPagination, PermissionsEnum } from '@gauzy/contracts'; import { CrudController, PaginationParams } from '../core/crud'; import { Permissions } from '../shared/decorators'; import { PermissionGuard, TenantPermissionGuard } from '../shared/guards'; import { UseValidationPipe, UUIDValidationPipe } from '../shared/pipes'; import { EmployeeAvailabilityService } from './employee-availability.service'; import { EmployeeAvailability } from './employee-availability.entity'; -import { EmployeeAvailabilityBulkCreateCommand, EmployeeAvailabilityCreateCommand } from './commands'; +import { EmployeeAvailabilityBulkCreateCommand, EMPLOYEE_AVAILABILITY_CREATECommand } from './commands'; import { CreateEmployeeAvailabilityDTO } from './dto/create-employee-availability.dto'; import { UpdateEmployeeAvailabilityDTO } from './dto/update-employee-availability.dto'; @ApiTags('EmployeeAvailability') @UseGuards(TenantPermissionGuard, PermissionGuard) +@Permissions(PermissionsEnum.EMPLOYEE_AVAILABILITY_UPDATE, PermissionsEnum.EMPLOYEE_AVAILABILITY_DELETE) @Controller('/employee-availability') export class EmployeeAvailabilityController extends CrudController { constructor( @@ -40,7 +41,7 @@ export class EmployeeAvailabilityController extends CrudController { @@ -62,8 +63,8 @@ export class EmployeeAvailabilityController extends CrudController @@ -87,11 +88,11 @@ export class EmployeeAvailabilityController extends CrudController { - return this.commandBus.execute(new EmployeeAvailabilityCreateCommand(entity)); + return this.commandBus.execute(new EMPLOYEE_AVAILABILITY_CREATECommand(entity)); } /** @@ -111,8 +112,8 @@ export class EmployeeAvailabilityController extends CrudController Date: Mon, 27 Jan 2025 11:31:23 +0530 Subject: [PATCH 15/16] feat: #5293 added permission for Employee Availability --- .../employee-availability.controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/lib/employee-availability/employee-availability.controller.ts b/packages/core/src/lib/employee-availability/employee-availability.controller.ts index 6b2ed052381..7691b0b7ee2 100644 --- a/packages/core/src/lib/employee-availability/employee-availability.controller.ts +++ b/packages/core/src/lib/employee-availability/employee-availability.controller.ts @@ -9,7 +9,7 @@ import { PermissionGuard, TenantPermissionGuard } from '../shared/guards'; import { UseValidationPipe, UUIDValidationPipe } from '../shared/pipes'; import { EmployeeAvailabilityService } from './employee-availability.service'; import { EmployeeAvailability } from './employee-availability.entity'; -import { EmployeeAvailabilityBulkCreateCommand, EMPLOYEE_AVAILABILITY_CREATECommand } from './commands'; +import { EmployeeAvailabilityBulkCreateCommand, EmployeeAvailabilityCreateCommand } from './commands'; import { CreateEmployeeAvailabilityDTO } from './dto/create-employee-availability.dto'; import { UpdateEmployeeAvailabilityDTO } from './dto/update-employee-availability.dto'; @@ -63,7 +63,7 @@ export class EmployeeAvailabilityController extends CrudController { - return this.commandBus.execute(new EMPLOYEE_AVAILABILITY_CREATECommand(entity)); + return this.commandBus.execute(new EmployeeAvailabilityCreateCommand(entity)); } /** From d8ece61c59d13e18d9be2f949f1c3b67869d0777 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Mon, 27 Jan 2025 12:50:17 +0530 Subject: [PATCH 16/16] feat: [table migration] for employee availability for all DB types. --- ...8313399-CreateEmployeeAvailabilityTable.ts | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 packages/core/src/lib/database/migrations/1737958313399-CreateEmployeeAvailabilityTable.ts diff --git a/packages/core/src/lib/database/migrations/1737958313399-CreateEmployeeAvailabilityTable.ts b/packages/core/src/lib/database/migrations/1737958313399-CreateEmployeeAvailabilityTable.ts new file mode 100644 index 00000000000..912ccd14450 --- /dev/null +++ b/packages/core/src/lib/database/migrations/1737958313399-CreateEmployeeAvailabilityTable.ts @@ -0,0 +1,227 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import * as chalk from 'chalk'; +import { DatabaseTypeEnum } from '@gauzy/config'; + +export class CreateEmployeeAvailabilityTable1737958313399 implements MigrationInterface { + name = 'CreateEmployeeAvailabilityTable1737958313399'; + + /** + * Up Migration + * + * @param queryRunner + */ + public async up(queryRunner: QueryRunner): Promise { + console.log(chalk.yellow(this.name + ' start running!')); + + switch (queryRunner.connection.options.type) { + case DatabaseTypeEnum.sqlite: + case DatabaseTypeEnum.betterSqlite3: + await this.sqliteUpQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.postgres: + await this.postgresUpQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.mysql: + await this.mysqlUpQueryRunner(queryRunner); + break; + default: + throw Error(`Unsupported database: ${queryRunner.connection.options.type}`); + } + } + + /** + * Down Migration + * + * @param queryRunner + */ + public async down(queryRunner: QueryRunner): Promise { + switch (queryRunner.connection.options.type) { + case DatabaseTypeEnum.sqlite: + case DatabaseTypeEnum.betterSqlite3: + await this.sqliteDownQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.postgres: + await this.postgresDownQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.mysql: + await this.mysqlDownQueryRunner(queryRunner); + break; + default: + throw Error(`Unsupported database: ${queryRunner.connection.options.type}`); + } + } + + /** + * PostgresDB Up Migration + * + * @param queryRunner + */ + public async postgresUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "employee_availability" ("deletedAt" TIMESTAMP, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "id" uuid NOT NULL DEFAULT gen_random_uuid(), "isActive" boolean DEFAULT true, "isArchived" boolean DEFAULT false, "archivedAt" TIMESTAMP, "tenantId" uuid, "organizationId" uuid, "startDate" TIMESTAMP NOT NULL, "endDate" TIMESTAMP NOT NULL, "dayOfWeek" integer NOT NULL, "availabilityStatus" integer NOT NULL, "availabilityNotes" text, "employeeId" uuid NOT NULL, CONSTRAINT "PK_8c252ae622cac6f708bc79aa3e7" PRIMARY KEY ("id"))` + ); + await queryRunner.query( + `CREATE INDEX "IDX_4df1dc5482972ff5344b670f78" ON "employee_availability" ("isActive") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_1c5a1979ce3d47b895f11f7395" ON "employee_availability" ("isArchived") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_981ccd9a51cc8706cbf8cdbdfb" ON "employee_availability" ("tenantId") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_9324ccb5291bd98d5fba6349f7" ON "employee_availability" ("organizationId") ` + ); + await queryRunner.query( + `ALTER TABLE "employee_availability" ADD CONSTRAINT "FK_981ccd9a51cc8706cbf8cdbdfb6" FOREIGN KEY ("tenantId") REFERENCES "tenant"("id") ON DELETE CASCADE ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE "employee_availability" ADD CONSTRAINT "FK_9324ccb5291bd98d5fba6349f75" FOREIGN KEY ("organizationId") REFERENCES "organization"("id") ON DELETE CASCADE ON UPDATE CASCADE` + ); + await queryRunner.query( + `ALTER TABLE "employee_availability" ADD CONSTRAINT "FK_63a5d274ac6ca68482e50f8f99a" FOREIGN KEY ("employeeId") REFERENCES "employee"("id") ON DELETE CASCADE ON UPDATE NO ACTION` + ); + } + + /** + * PostgresDB Down Migration + * + * @param queryRunner + */ + public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "employee_availability" DROP CONSTRAINT "FK_63a5d274ac6ca68482e50f8f99a"`); + await queryRunner.query(`ALTER TABLE "employee_availability" DROP CONSTRAINT "FK_9324ccb5291bd98d5fba6349f75"`); + await queryRunner.query(`ALTER TABLE "employee_availability" DROP CONSTRAINT "FK_981ccd9a51cc8706cbf8cdbdfb6"`); + await queryRunner.query(`DROP INDEX "public"."IDX_9324ccb5291bd98d5fba6349f7"`); + await queryRunner.query(`DROP INDEX "public"."IDX_981ccd9a51cc8706cbf8cdbdfb"`); + await queryRunner.query(`DROP INDEX "public"."IDX_1c5a1979ce3d47b895f11f7395"`); + await queryRunner.query(`DROP INDEX "public"."IDX_4df1dc5482972ff5344b670f78"`); + await queryRunner.query(`DROP TABLE "employee_availability"`); + } + + /** + * SqliteDB and BetterSQlite3DB Up Migration + * + * @param queryRunner + */ + public async sqliteUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "employee_availability" ("deletedAt" datetime, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "id" varchar PRIMARY KEY NOT NULL, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "archivedAt" datetime, "tenantId" varchar, "organizationId" varchar, "startDate" datetime NOT NULL, "endDate" datetime NOT NULL, "dayOfWeek" integer NOT NULL, "availabilityStatus" integer NOT NULL, "availabilityNotes" text, "employeeId" varchar NOT NULL)` + ); + await queryRunner.query( + `CREATE INDEX "IDX_4df1dc5482972ff5344b670f78" ON "employee_availability" ("isActive") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_1c5a1979ce3d47b895f11f7395" ON "employee_availability" ("isArchived") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_981ccd9a51cc8706cbf8cdbdfb" ON "employee_availability" ("tenantId") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_9324ccb5291bd98d5fba6349f7" ON "employee_availability" ("organizationId") ` + ); + await queryRunner.query(`DROP INDEX "IDX_4df1dc5482972ff5344b670f78"`); + await queryRunner.query(`DROP INDEX "IDX_1c5a1979ce3d47b895f11f7395"`); + await queryRunner.query(`DROP INDEX "IDX_981ccd9a51cc8706cbf8cdbdfb"`); + await queryRunner.query(`DROP INDEX "IDX_9324ccb5291bd98d5fba6349f7"`); + await queryRunner.query( + `CREATE TABLE "temporary_employee_availability" ("deletedAt" datetime, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "id" varchar PRIMARY KEY NOT NULL, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "archivedAt" datetime, "tenantId" varchar, "organizationId" varchar, "startDate" datetime NOT NULL, "endDate" datetime NOT NULL, "dayOfWeek" integer NOT NULL, "availabilityStatus" integer NOT NULL, "availabilityNotes" text, "employeeId" varchar NOT NULL, CONSTRAINT "FK_981ccd9a51cc8706cbf8cdbdfb6" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_9324ccb5291bd98d5fba6349f75" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_63a5d274ac6ca68482e50f8f99a" FOREIGN KEY ("employeeId") REFERENCES "employee" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_employee_availability"("deletedAt", "createdAt", "updatedAt", "id", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "startDate", "endDate", "dayOfWeek", "availabilityStatus", "availabilityNotes", "employeeId") SELECT "deletedAt", "createdAt", "updatedAt", "id", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "startDate", "endDate", "dayOfWeek", "availabilityStatus", "availabilityNotes", "employeeId" FROM "employee_availability"` + ); + await queryRunner.query(`DROP TABLE "employee_availability"`); + await queryRunner.query(`ALTER TABLE "temporary_employee_availability" RENAME TO "employee_availability"`); + await queryRunner.query( + `CREATE INDEX "IDX_4df1dc5482972ff5344b670f78" ON "employee_availability" ("isActive") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_1c5a1979ce3d47b895f11f7395" ON "employee_availability" ("isArchived") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_981ccd9a51cc8706cbf8cdbdfb" ON "employee_availability" ("tenantId") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_9324ccb5291bd98d5fba6349f7" ON "employee_availability" ("organizationId") ` + ); + } + + /** + * SqliteDB and BetterSQlite3DB Down Migration + * + * @param queryRunner + */ + public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_9324ccb5291bd98d5fba6349f7"`); + await queryRunner.query(`DROP INDEX "IDX_981ccd9a51cc8706cbf8cdbdfb"`); + await queryRunner.query(`DROP INDEX "IDX_1c5a1979ce3d47b895f11f7395"`); + await queryRunner.query(`DROP INDEX "IDX_4df1dc5482972ff5344b670f78"`); + await queryRunner.query(`ALTER TABLE "employee_availability" RENAME TO "temporary_employee_availability"`); + await queryRunner.query( + `CREATE TABLE "employee_availability" ("deletedAt" datetime, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "id" varchar PRIMARY KEY NOT NULL, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "archivedAt" datetime, "tenantId" varchar, "organizationId" varchar, "startDate" datetime NOT NULL, "endDate" datetime NOT NULL, "dayOfWeek" integer NOT NULL, "availabilityStatus" integer NOT NULL, "availabilityNotes" text, "employeeId" varchar NOT NULL)` + ); + await queryRunner.query( + `INSERT INTO "employee_availability"("deletedAt", "createdAt", "updatedAt", "id", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "startDate", "endDate", "dayOfWeek", "availabilityStatus", "availabilityNotes", "employeeId") SELECT "deletedAt", "createdAt", "updatedAt", "id", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "startDate", "endDate", "dayOfWeek", "availabilityStatus", "availabilityNotes", "employeeId" FROM "temporary_employee_availability"` + ); + await queryRunner.query(`DROP TABLE "temporary_employee_availability"`); + await queryRunner.query( + `CREATE INDEX "IDX_9324ccb5291bd98d5fba6349f7" ON "employee_availability" ("organizationId") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_981ccd9a51cc8706cbf8cdbdfb" ON "employee_availability" ("tenantId") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_1c5a1979ce3d47b895f11f7395" ON "employee_availability" ("isArchived") ` + ); + await queryRunner.query( + `CREATE INDEX "IDX_4df1dc5482972ff5344b670f78" ON "employee_availability" ("isActive") ` + ); + await queryRunner.query(`DROP INDEX "IDX_9324ccb5291bd98d5fba6349f7"`); + await queryRunner.query(`DROP INDEX "IDX_981ccd9a51cc8706cbf8cdbdfb"`); + await queryRunner.query(`DROP INDEX "IDX_1c5a1979ce3d47b895f11f7395"`); + await queryRunner.query(`DROP INDEX "IDX_4df1dc5482972ff5344b670f78"`); + await queryRunner.query(`DROP TABLE "employee_availability"`); + } + + /** + * MySQL Up Migration + * + * @param queryRunner + */ + public async mysqlUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`employee_availability\` (\`deletedAt\` datetime(6) NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`id\` varchar(36) NOT NULL, \`isActive\` tinyint NULL DEFAULT 1, \`isArchived\` tinyint NULL DEFAULT 0, \`archivedAt\` datetime NULL, \`tenantId\` varchar(255) NULL, \`organizationId\` varchar(255) NULL, \`startDate\` datetime NOT NULL, \`endDate\` datetime NOT NULL, \`dayOfWeek\` int NOT NULL, \`availabilityStatus\` int NOT NULL, \`availabilityNotes\` text NULL, \`employeeId\` varchar(255) NOT NULL, INDEX \`IDX_4df1dc5482972ff5344b670f78\` (\`isActive\`), INDEX \`IDX_1c5a1979ce3d47b895f11f7395\` (\`isArchived\`), INDEX \`IDX_981ccd9a51cc8706cbf8cdbdfb\` (\`tenantId\`), INDEX \`IDX_9324ccb5291bd98d5fba6349f7\` (\`organizationId\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `ALTER TABLE \`employee_availability\` ADD CONSTRAINT \`FK_981ccd9a51cc8706cbf8cdbdfb6\` FOREIGN KEY (\`tenantId\`) REFERENCES \`tenant\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`employee_availability\` ADD CONSTRAINT \`FK_9324ccb5291bd98d5fba6349f75\` FOREIGN KEY (\`organizationId\`) REFERENCES \`organization\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE` + ); + await queryRunner.query( + `ALTER TABLE \`employee_availability\` ADD CONSTRAINT \`FK_63a5d274ac6ca68482e50f8f99a\` FOREIGN KEY (\`employeeId\`) REFERENCES \`employee\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + } + + /** + * MySQL Down Migration + * + * @param queryRunner + */ + public async mysqlDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`employee_availability\` DROP FOREIGN KEY \`FK_63a5d274ac6ca68482e50f8f99a\`` + ); + await queryRunner.query( + `ALTER TABLE \`employee_availability\` DROP FOREIGN KEY \`FK_9324ccb5291bd98d5fba6349f75\`` + ); + await queryRunner.query( + `ALTER TABLE \`employee_availability\` DROP FOREIGN KEY \`FK_981ccd9a51cc8706cbf8cdbdfb6\`` + ); + await queryRunner.query(`DROP INDEX \`IDX_9324ccb5291bd98d5fba6349f7\` ON \`employee_availability\``); + await queryRunner.query(`DROP INDEX \`IDX_981ccd9a51cc8706cbf8cdbdfb\` ON \`employee_availability\``); + await queryRunner.query(`DROP INDEX \`IDX_1c5a1979ce3d47b895f11f7395\` ON \`employee_availability\``); + await queryRunner.query(`DROP INDEX \`IDX_4df1dc5482972ff5344b670f78\` ON \`employee_availability\``); + await queryRunner.query(`DROP TABLE \`employee_availability\``); + } +}