/* eslint-disable @typescript-eslint/naming-convention */ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import moment from 'moment'; import { And, Brackets, type FindOptionsWhere, LessThan, MoreThanOrEqual, Repository, } from 'typeorm'; import { type PageDto } from '../../common/dto/page.dto'; import { AccountHasClosedException, DateJoinLaterThanDateResignException, EmployeeNotFoundException, EmployeeReferralByOwnException, ReferralByException, ReferralFeeHasPaidException, } from '../../exceptions'; import { ActivityRecordService } from '../activity-record/activity-record.service'; import { ActivityRecordSettingService } from '../activity-record/activity-record-setting.service'; import { BlockService } from '../block/block.service'; import { PublicHolidayService } from '../public-holiday/public-holiday.service'; import { type GetByEmployeeDto } from '../report/dtos/get-report-by-employee.dto'; import { UserService } from '../user/user.service'; import { type CreateEmployeeDto } from './dtos/create-employee.dto'; import { type EmployeeDto } from './dtos/employee.dto'; import { type EmployeePageOptionsDto } from './dtos/get-employee-page.dto'; import { type UpdateEmployeeDto } from './dtos/update-employee.dto'; import { EmployeeEntity } from './employee.entity'; @Injectable() export class EmployeeService { constructor( @InjectRepository(EmployeeEntity) private employeeReposiory: Repository<EmployeeEntity>, @Inject(forwardRef(() => ActivityRecordSettingService)) private activitySettingService: ActivityRecordSettingService, @Inject(forwardRef(() => ActivityRecordService)) private activityService: ActivityRecordService, @Inject(forwardRef(() => BlockService)) private blockService: BlockService, @Inject(forwardRef(() => PublicHolidayService)) private publicHolidayService: PublicHolidayService, private userService: UserService, ) {} async findOne( findData: FindOptionsWhere<EmployeeEntity>, ): Promise<EmployeeEntity | null> { const queryBuilder = this.employeeReposiory.createQueryBuilder('employee'); queryBuilder.where(findData); return queryBuilder.getOne(); } async findMany( pageOptionsDto: EmployeePageOptionsDto, ): Promise<PageDto<EmployeeDto>> { const { order, isActive, name, origin, referralBy, referralFeePaid } = pageOptionsDto; const queryBuilder = this.employeeReposiory.createQueryBuilder('employee'); if (isActive !== undefined) { queryBuilder.andWhere('employee.isActive = :isActive', { isActive }); } if (name !== undefined) { queryBuilder.andWhere('employee.name ILIKE :name', { name: `%${name}%`, }); } if (origin !== undefined) { queryBuilder.andWhere('employee.origin ILIKE :origin', { origin: `%${origin}%`, }); } if (referralBy !== undefined) { queryBuilder.andWhere('employee.referralBy ILIKE :referralBy', { referralBy: `%${referralBy}%`, }); } if (referralFeePaid !== undefined) { queryBuilder.andWhere( `employee.referralFeePaidAt IS ${referralFeePaid ? 'NOT' : ''} NULL`, { referralFeePaid }, ); } queryBuilder.orderBy('employee.name', order); const [items, pageMetaDto] = await queryBuilder.paginate(pageOptionsDto); return items.toPageDto(pageMetaDto); } async findManyWithoutPagination( findData?: FindOptionsWhere<EmployeeEntity>, ): Promise<EmployeeEntity[]> { const queryBuilder = this.employeeReposiory.createQueryBuilder('employee'); if (findData) { queryBuilder.where(findData); } queryBuilder.orderBy('employee.name', 'ASC'); return queryBuilder.getMany(); } async findManyDropdown({ date, shouldFilter = true, showInactive = false, }: { date?: Date; shouldFilter?: boolean; showInactive?: boolean; }): Promise<Array<{ name: string; value: string | null }>> { const requestDate = date ? moment.utc(date).startOf('date') : moment.utc().startOf('date'); const queryBuilder = this.employeeReposiory.createQueryBuilder('employee'); if (!showInactive && shouldFilter) { queryBuilder.andWhere('employee.isActive = :status', { status: true }); queryBuilder.andWhere( new Brackets((qb) => qb .andWhere('employee.dateResign IS NULL') .orWhere('employee.dateResign >= :requestDate', { requestDate }), ), ); } queryBuilder.orderBy('employee.name', 'ASC'); const returnData = await queryBuilder.getMany(); return returnData.map((el) => ({ name: el.name, value: el.name, })); } async findManyByReportFunction( dto: GetByEmployeeDto, ): Promise<EmployeeEntity[]> { // eslint-disable-next-line @typescript-eslint/naming-convention const { isActive, name, order, origin, referralBy, referralFeePaid, year, monthTo, monthFrom, } = dto; const queryBuilder = this.employeeReposiory.createQueryBuilder('employee'); const currentYear = year ?? moment.utc().year(); if (monthTo && monthFrom) { const fromDate = moment .utc({ year: currentYear, month: monthFrom - 1, date: 1, }) .toDate(); const toDate = moment .utc({ year: currentYear, month: monthTo - 1, date: 1 }) .add(1, 'months') .toDate(); queryBuilder.andWhere('employee.dateJoin <= :toDate', { toDate }); queryBuilder.andWhere( new Brackets((qb) => qb .orWhere('employee.dateResign IS NULL') .orWhere('employee.dateResign >= :fromDate', { fromDate }), ), ); } if (isActive !== undefined) { queryBuilder.andWhere('employee.isActive = :isActive', { isActive }); } if (name) { queryBuilder.andWhere('employee.name = :name', { name }); } if (origin) { queryBuilder.andWhere('employee.origin = :origin', { origin }); } if (referralBy) { queryBuilder.andWhere('employee.referralBy = :referralBy', { referralBy, }); } if (referralFeePaid !== undefined) { queryBuilder.andWhere('employee.referralFeePaidAt IS NOT NULL'); } queryBuilder.orderBy('employee.name', order); return queryBuilder.getMany(); } async findManyReferralNotPaid(): Promise<EmployeeEntity[]> { const queryBuilder = this.employeeReposiory.createQueryBuilder('employee'); queryBuilder.where('employee.isActive = :status', { status: true }); queryBuilder.andWhere('employee.referralBy IS NOT NULL'); queryBuilder.andWhere('employee.referralFeePaidAt IS NULL'); return queryBuilder.getMany(); } // eslint-disable-next-line sonarjs/cognitive-complexity async createEmployee(dto: CreateEmployeeDto): Promise<EmployeeEntity> { const { referralBy, basicSalary, referralFeePaidAt } = dto; if (referralBy) { const existingEmployee = await this.findOne({ name: referralBy, }); if (!existingEmployee) { throw new EmployeeNotFoundException(); } } if (!referralBy && referralFeePaidAt) { throw new ReferralByException(); } const employeeEntity = this.employeeReposiory.create({ ...dto, dailyRateAmount: basicSalary / 30, }); if ( employeeEntity.dateJoin < moment.utc().startOf('month').add(-1, 'month').toDate() || (employeeEntity.dateResign && employeeEntity.dateResign < moment.utc().startOf('month').add(-1, 'month').toDate()) ) { throw new AccountHasClosedException(); } if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition employeeEntity.dateResign && employeeEntity.dateJoin > employeeEntity.dateResign ) { throw new DateJoinLaterThanDateResignException(); } await this.employeeReposiory.save(employeeEntity); const publicHolidaySetting = await this.activitySettingService.findOne({ name: 'Public Holiday', }); const block = await this.blockService.findOne({ name: 'none', }); const user = await this.userService.findFirstUser(); const toCheckDate = moment.utc().startOf('month').add(-1, 'month').toDate() > employeeEntity.dateJoin ? moment.utc().startOf('month').add(-1, 'month').toDate() : employeeEntity.dateJoin; if (publicHolidaySetting && block) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const publicHolidayArr = await (employeeEntity.dateResign ? this.publicHolidayService.findManyWithoutPagination({ date: And( MoreThanOrEqual(toCheckDate), LessThan(employeeEntity.dateResign), ), }) : this.publicHolidayService.findManyWithoutPagination({ date: MoreThanOrEqual(toCheckDate), })); for (const publicHoloday of publicHolidayArr) { // eslint-disable-next-line no-await-in-loop await this.activityService.createActivityRecord( { activityName: 'Public Holiday', blockName: 'none', date: publicHoloday.date, employeeName: employeeEntity.name, hour: 8, quantity: 8, }, user!, employeeEntity, ); } } return employeeEntity; } // eslint-disable-next-line sonarjs/cognitive-complexity async updateEmployee( employeeEntity: EmployeeEntity, dto: UpdateEmployeeDto, ): Promise<EmployeeEntity> { const { basicSalary, dateJoin, dateResign, epfRatePer, monthlyAllowanceAmount, name, origin, shouldDeductSocso, isActive, referralBy, referralFeePaidAt, annualLeave, sickLeave, } = dto; const beforeUpdateEmployee = { ...employeeEntity }; if (basicSalary !== undefined) { employeeEntity.basicSalary = basicSalary; employeeEntity.dailyRateAmount = basicSalary / 30; } if (epfRatePer !== undefined) { employeeEntity.epfRatePer = epfRatePer; } if (monthlyAllowanceAmount !== undefined) { employeeEntity.monthlyAllowanceAmount = monthlyAllowanceAmount; } if (annualLeave !== undefined) { employeeEntity.annualLeave = annualLeave; } if (sickLeave !== undefined) { employeeEntity.sickLeave = sickLeave; } if (name) { employeeEntity.name = name; } if (origin !== undefined) { employeeEntity.origin = origin; } if (isActive !== undefined) { employeeEntity.isActive = isActive; } if (referralBy !== undefined) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (employeeEntity.referralFeePaidAt && referralBy === null) { throw new ReferralFeeHasPaidException(); } if (referralBy === employeeEntity.name) { throw new EmployeeReferralByOwnException(); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (referralBy !== null) { const existingEmployee = await this.findOne({ name: referralBy, }); if (!existingEmployee) { throw new EmployeeNotFoundException(); } } employeeEntity.referralBy = referralBy; } if (referralFeePaidAt !== undefined) { if ( beforeUpdateEmployee.referralFeePaidAt && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition referralFeePaidAt === null ) { throw new ReferralFeeHasPaidException(); } employeeEntity.referralFeePaidAt = referralFeePaidAt; } if (shouldDeductSocso !== undefined) { employeeEntity.shouldDeductSocso = shouldDeductSocso; } if (dateJoin) { employeeEntity.dateJoin = dateJoin; } if (dateResign !== undefined) { employeeEntity.dateResign = dateResign; if ( employeeEntity.dateJoin < moment.utc().startOf('month').add(-1, 'month').toDate() || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (employeeEntity.dateResign && employeeEntity.dateResign < moment.utc().startOf('month').add(-1, 'month').toDate()) ) { throw new AccountHasClosedException(); } if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition employeeEntity.dateResign && employeeEntity.dateJoin > employeeEntity.dateResign ) { throw new DateJoinLaterThanDateResignException(); } const publicHolidaySetting = await this.activitySettingService.findOne({ name: 'Public Holiday', }); const block = await this.blockService.findOne({ name: 'none', }); const user = await this.userService.findFirstUser(); const toCheckDate = moment.utc().startOf('month').add(-1, 'month').toDate() > employeeEntity.dateJoin ? moment.utc().startOf('month').add(-1, 'month').toDate() : employeeEntity.dateJoin; if (publicHolidaySetting && block) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const publicHolidayArr = await this.publicHolidayService.findManyWithoutPagination({ date: MoreThanOrEqual(toCheckDate), }); for (const publicHoloday of publicHolidayArr) { // eslint-disable-next-line no-await-in-loop const existingRecord = await this.activityService.findOne( { activityRecordSetting: { id: publicHolidaySetting.id }, block: { id: block.id }, date: publicHoloday.date, employee: { id: employeeEntity.id }, }, true, ); if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition dateResign !== null && publicHoloday.date > dateResign && existingRecord ) { // eslint-disable-next-line no-await-in-loop await this.activityService.deleteActivityRecord( existingRecord, user!, ); continue; } if (existingRecord) { continue; } // eslint-disable-next-line no-await-in-loop await this.activityService.createActivityRecord( { activityName: 'Public Holiday', blockName: 'none', date: publicHoloday.date, employeeName: employeeEntity.name, hour: 8, quantity: 8, }, user!, employeeEntity, ); } } } await this.employeeReposiory.save(employeeEntity); return employeeEntity; } }