Add employees management module with database schema and validation

- Introduced `EmployeesModule` to manage employee data, including read and write controllers.
- Created database migrations for the `employees` table, including constraints and unique indexes.
- Implemented validation for employee fields such as name, code, and position with corresponding utility functions.
- Developed service and repository layers for handling employee data operations.
- Added unit tests for the employees service, repository, and controllers to ensure functionality and correctness.
- Updated application module to include the new `EmployeesModule` for better organization.
This commit is contained in:
shancheas
2026-08-24 15:15:52 +07:00
parent cdcc508947
commit ddbe8a9ef8
20 changed files with 3355 additions and 2 deletions
@@ -0,0 +1,183 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
ArrayNotEmpty,
IsArray,
IsIn,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
Matches,
MaxLength,
} from 'class-validator';
import { PaginationQueryDto } from '../../../../common/http/response';
import { CORE_STATUSES } from '../../../../common/value-objects/status/status';
import {
EMPLOYEE_CODE_MAX_LENGTH,
EMPLOYEE_CODE_PATTERN,
EMPLOYEE_NAME_MAX_LENGTH,
EMPLOYEE_NAME_PATTERN,
EMPLOYEE_POSITIONS,
} from '../employee-fields';
export class CreateEmployeeDto {
@ApiProperty({ example: 'EMP_01', maxLength: EMPLOYEE_CODE_MAX_LENGTH })
@IsString()
@IsNotEmpty()
@MaxLength(EMPLOYEE_CODE_MAX_LENGTH)
@Matches(EMPLOYEE_CODE_PATTERN, {
message: 'code must contain only letters, numbers, and underscores',
})
code!: string;
@ApiProperty({
example: 'Ada Lovelace',
maxLength: EMPLOYEE_NAME_MAX_LENGTH,
})
@IsString()
@IsNotEmpty()
@MaxLength(EMPLOYEE_NAME_MAX_LENGTH)
@Matches(EMPLOYEE_NAME_PATTERN, {
message: 'name must contain only letters and spaces',
})
name!: string;
@ApiProperty({ example: '+6281234567890' })
@IsString()
@IsNotEmpty()
phone!: string;
@ApiProperty({ enum: EMPLOYEE_POSITIONS, example: 'sales' })
@IsIn([...EMPLOYEE_POSITIONS])
position!: string;
@ApiPropertyOptional({ enum: CORE_STATUSES })
@IsOptional()
@IsIn([...CORE_STATUSES])
status?: string;
}
export class UpdateEmployeeDto {
@ApiPropertyOptional({ example: 'EMP_01' })
@IsOptional()
@IsString()
@IsNotEmpty()
@MaxLength(EMPLOYEE_CODE_MAX_LENGTH)
@Matches(EMPLOYEE_CODE_PATTERN, {
message: 'code must contain only letters, numbers, and underscores',
})
code?: string;
@ApiPropertyOptional({ example: 'Ada Lovelace' })
@IsOptional()
@IsString()
@IsNotEmpty()
@MaxLength(EMPLOYEE_NAME_MAX_LENGTH)
@Matches(EMPLOYEE_NAME_PATTERN, {
message: 'name must contain only letters and spaces',
})
name?: string;
@ApiPropertyOptional({ example: '+6281234567890' })
@IsOptional()
@IsString()
@IsNotEmpty()
phone?: string;
@ApiPropertyOptional({ enum: EMPLOYEE_POSITIONS })
@IsOptional()
@IsIn([...EMPLOYEE_POSITIONS])
position?: string;
}
export class UpdateEmployeeStatusDto {
@ApiProperty({ enum: CORE_STATUSES })
@IsIn([...CORE_STATUSES])
status!: string;
}
export class BulkIdsDto {
@ApiProperty({ type: [String], format: 'uuid' })
@IsArray()
@ArrayNotEmpty()
@IsUUID('4', { each: true })
ids!: string[];
}
export class BulkStatusDto {
@ApiProperty({ type: [String], format: 'uuid' })
@IsArray()
@ArrayNotEmpty()
@IsUUID('4', { each: true })
ids!: string[];
@ApiProperty({ enum: CORE_STATUSES })
@IsIn([...CORE_STATUSES])
status!: string;
}
export class ListEmployeesQueryDto extends PaginationQueryDto {
@ApiPropertyOptional()
@IsOptional()
@IsString()
code?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
name?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
phone?: string;
@ApiPropertyOptional({ enum: EMPLOYEE_POSITIONS })
@IsOptional()
@IsIn([...EMPLOYEE_POSITIONS])
position?: string;
@ApiPropertyOptional({ enum: CORE_STATUSES })
@IsOptional()
@IsIn([...CORE_STATUSES])
status?: string;
@ApiPropertyOptional({
description: 'Case-insensitive match on code or name',
})
@IsOptional()
@IsString()
search?: string;
}
export class EmployeeDto {
@ApiProperty({ format: 'uuid' })
id!: string;
@ApiProperty()
code!: string;
@ApiProperty()
name!: string;
@ApiProperty({ example: '+6281234567890' })
phone!: string;
@ApiProperty({ enum: EMPLOYEE_POSITIONS })
position!: string;
@ApiProperty({ enum: CORE_STATUSES })
status!: string;
@ApiProperty({ description: 'Unix ms' })
createdAt!: number;
@ApiProperty({ description: 'Unix ms' })
updatedAt!: number;
@ApiProperty({ format: 'uuid' })
createdBy!: string;
@ApiProperty({ format: 'uuid' })
updatedBy!: string;
}