penisularhr / src / modules / employee / employee.service.ts
employee.service.ts
Raw
/* 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;
  }
}