penisularhr / src / modules / activity-record / activity-record.service.ts
activity-record.service.ts
Raw
import { InjectQueue } from '@nestjs/bull';
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Queue } from 'bull';
import moment from 'moment';
import { type FindOptionsWhere, Repository } from 'typeorm';

import { type PageDto } from '../../common/dto/page.dto';
import { IncentiveName, OperateType } from '../../constants';
import {
  ActivityHourExceedException,
  ActivityRecordSettingNotActiveException,
  ActivityRecordSettingNotFoundException,
  BlockNotActiveException,
  BlockNotFoundException,
  DeductionExceedLeaveException,
  DuplicateActivityRecordException,
  EmployeeAlreadyResignedException,
  EmployeeNotActiveException,
  EmployeeNotFoundException,
  SectorNotFoundException,
} from '../../exceptions';
import { type AdminConfigEntity } from '../admin-config/admin-config..entity';
import { AuditLogService } from '../audit-log/audit-log.service';
import { BlockService } from '../block/block.service';
import { type EmployeeEntity } from '../employee/employee.entity';
import { EmployeeService } from '../employee/employee.service';
import { IncentiveRecordService } from '../incentive/incentive-record.service';
import { type GetActivityDto } from '../report/dtos/get-report-activity.dto';
import { type GetByEmployeeDto } from '../report/dtos/get-report-by-employee.dto';
import { SectorService } from '../sector/sector.service';
import { type UserEntity } from '../user/user.entity';
import { ActivityRecordEntity } from './activity-record.entity';
import { ActivityRecordSettingService } from './activity-record-setting.service';
import { type ActivityRecordDto } from './dtos/activity-record.dto';
import { type CreateActivityRecordDto } from './dtos/create-activity-record.dto';
import { type ActivityRecordPageOptionsDto } from './dtos/get-activity-record-page.dto';
import { type UpdateActivityRecordDto } from './dtos/update-activity-record.dto';

@Injectable()
export class ActivityRecordService {
  constructor(
    @InjectRepository(ActivityRecordEntity)
    private activityRecordReposiory: Repository<ActivityRecordEntity>,
    @Inject(forwardRef(() => IncentiveRecordService))
    private incentiveRecordService: IncentiveRecordService,
    @Inject(forwardRef(() => ActivityRecordSettingService))
    private activityRecordSettingService: ActivityRecordSettingService,
    private blockService: BlockService,
    private employeeService: EmployeeService,
    private sectorService: SectorService,
    private auditLogService: AuditLogService,
    @InjectQueue('advanceAndRestDayWages')
    private advanceAndRestDayWagesQueue: Queue,
  ) {}

  async findOne(
    findData: FindOptionsWhere<ActivityRecordEntity>,
    shouldLeftJoin?: boolean,
  ): Promise<ActivityRecordEntity | null> {
    const queryBuilder =
      this.activityRecordReposiory.createQueryBuilder('activityRecord');

    queryBuilder.where(findData);

    if (shouldLeftJoin) {
      queryBuilder
        .leftJoin('activityRecord.employee', 'employee')
        .addSelect(['employee.id', 'employee.name'])
        .leftJoin('activityRecord.activityRecordSetting', 'setting')
        .addSelect(['setting.id', 'setting.incentiveName', 'setting.name'])
        .leftJoin('setting.activityGroup', 'activityGroup')
        .addSelect(['activityGroup.id', 'activityGroup.name']);
    }

    return queryBuilder.getOne();
  }

  async findMany(
    pageOptionsDto: ActivityRecordPageOptionsDto,
  ): Promise<PageDto<ActivityRecordDto>> {
    const {
      order,
      activityName,
      blockName,
      date,
      employeeName,
      incentiveName,
      sectorName,
      activityGroupName,
      id,
    } = pageOptionsDto;

    const queryBuilder =
      this.activityRecordReposiory.createQueryBuilder('activityRecord');

    queryBuilder
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect(['employee.id', 'employee.name'])
      .leftJoin('activityRecord.block', 'block')
      .addSelect(['block.name'])
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .addSelect([
        'setting.id',
        'setting.incentiveName',
        'setting.name',
        'setting.unit',
      ])
      .leftJoin('setting.sector', 'sector')
      .addSelect(['sector.name'])
      .leftJoin('setting.activityGroup', 'activityGroup')
      .addSelect(['activityGroup.name']);

    if (date !== undefined) {
      queryBuilder.andWhere('activityRecord.date = :date', { date });
    }

    if (id !== undefined) {
      queryBuilder.andWhere('activityRecord.id = :id', { id });
    }

    if (activityName !== undefined) {
      queryBuilder.andWhere('setting.name ILIKE :activityName', {
        activityName: `%${activityName}%`,
      });
    }

    if (blockName !== undefined) {
      queryBuilder.andWhere('block.name ILIKE :blockName', {
        blockName: `%${blockName}%`,
      });
    }

    if (employeeName !== undefined) {
      queryBuilder.andWhere('employee.name ILIKE :employeeName', {
        employeeName: `%${employeeName}%`,
      });
    }

    if (incentiveName !== undefined) {
      queryBuilder.andWhere('setting.incentiveName = :incentiveName', {
        incentiveName,
      });
    }

    if (activityGroupName !== undefined) {
      queryBuilder.andWhere('activityGroup.name ILIKE :activityGroupName', {
        activityGroupName: `%${activityGroupName}%`,
      });
    }

    if (sectorName !== undefined) {
      queryBuilder.andWhere('sector.name ILIKE :sectorName', {
        sectorName: `%${sectorName}%`,
      });
    }

    queryBuilder.orderBy('activityRecord.date', order);

    const [items, pageMetaDto] = await queryBuilder.paginate(pageOptionsDto);

    return items.toPageDto(pageMetaDto);
  }

  async findReportMany(dto: GetActivityDto) {
    const {
      activityName,
      employeeName,
      incentiveName,
      monthFrom,
      monthTo,
      order,
      blockName,
      sectorName,
      year,
      activityGroupName,
    } = dto;

    const queryBuilder =
      this.activityRecordReposiory.createQueryBuilder('activityRecord');

    const currentYear = year ?? moment.utc().year();

    queryBuilder
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect(['employee.name'])
      .leftJoin('activityRecord.block', 'block')
      .addSelect(['block.name'])
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .addSelect([
        'setting.id',
        'setting.incentiveName',
        'setting.name',
        'setting.unit',
      ])
      .leftJoin('setting.sector', 'sector')
      .addSelect(['sector.name'])
      .leftJoin('setting.activityGroup', 'activityGroup')
      .addSelect(['activityGroup.name']);

    if (activityName) {
      queryBuilder.andWhere('setting.name = :activityName', { activityName });
    }

    if (employeeName) {
      queryBuilder.andWhere('employee.name = :employeeName', { employeeName });
    }

    if (incentiveName) {
      queryBuilder.andWhere('setting.incentiveName = :incentiveName', {
        incentiveName,
      });
    }

    if (sectorName) {
      queryBuilder.andWhere('sector.name = :sectorName', { sectorName });
    }

    if (activityGroupName) {
      queryBuilder.andWhere('activityGroup.name = :activityGroupName', {
        activityGroupName,
      });
    }

    if (blockName) {
      queryBuilder.andWhere('block.name = :blockName', { blockName });
    }

    let fromDate: Date | undefined;
    let toDate: Date | undefined;

    if (monthFrom) {
      fromDate = moment
        .utc({
          year: currentYear,
          month: monthFrom - 1,
          date: 1,
        })
        .toDate();

      queryBuilder.andWhere('activityRecord.date >= :fromDate', { fromDate });
    }

    if (monthTo) {
      toDate = moment
        .utc({ year: currentYear, month: monthTo - 1, date: 1 })
        .add(1, 'months')
        .toDate();

      queryBuilder.andWhere('activityRecord.date < :toDate', { toDate });
    }

    queryBuilder.orderBy('activityRecord.date', order);

    const response = await queryBuilder.getMany();

    return response.map((el) => el.toDto());
  }

  async findByActivitySum(dto: GetActivityDto) {
    const { activityName, monthFrom, monthTo, year, order, sectorName } = dto;

    const queryBuilder =
      this.activityRecordReposiory.createQueryBuilder('activityRecord');

    const currentYear = year ?? moment.utc().year();

    queryBuilder
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .leftJoin('setting.sector', 'sector')
      .select('setting.name', 'name')
      .addSelect('setting.unit', 'unit')
      .addSelect('sector.name', 'sector')
      .addSelect('COALESCE(activityRecord.rate,0)', 'rate')
      .addSelect('COALESCE(SUM(activityRecord.hour),0)', 'hour')
      .addSelect('COALESCE(SUM(activityRecord.quantity),0)', 'quantity')
      .addSelect(
        'COALESCE(ROUND(SUM(activityRecord.rate * activityRecord.quantity),2),0)',
        'amount',
      );

    if (activityName) {
      queryBuilder.andWhere('setting.name = :activityName', { activityName });
    }

    if (sectorName) {
      queryBuilder.andWhere('sector.name = :sectorName', { sectorName });
    }

    let fromDate: Date | undefined;
    let toDate: Date | undefined;

    if (monthFrom) {
      fromDate = moment
        .utc({
          year: currentYear,
          month: monthFrom - 1,
          date: 1,
        })
        .toDate();

      queryBuilder.andWhere('activityRecord.date >= :fromDate', { fromDate });
    }

    if (monthTo) {
      toDate = moment
        .utc({ year: currentYear, month: monthTo - 1, date: 1 })
        .add(1, 'months')
        .toDate();

      queryBuilder.andWhere('activityRecord.date < :toDate', { toDate });
    }

    queryBuilder.groupBy('setting.name');
    queryBuilder.addGroupBy('setting.unit');
    queryBuilder.addGroupBy('activityRecord.rate');
    queryBuilder.addGroupBy('sector.name');

    queryBuilder.orderBy('setting.name', order);

    return queryBuilder.getRawMany();
  }

  // eslint-disable-next-line sonarjs/cognitive-complexity
  async createActivityRecord(
    dto: CreateActivityRecordDto,
    user: UserEntity,
    employee?: EmployeeEntity,
  ): Promise<ActivityRecordEntity> {
    const {
      date,
      quantity,
      employeeName,
      activityName,
      blockName,
      remark,
      hour,
    } = dto;

    const recordDate = moment.utc(date).startOf('date');

    // 1. Check if employee exist
    let employeeEntity = await this.employeeService.findOne({
      name: employeeName,
    });

    if (employee) {
      employeeEntity = employee;
    }

    if (!employeeEntity) {
      throw new EmployeeNotFoundException();
    }

    if (!employeeEntity.isActive) {
      throw new EmployeeNotActiveException();
    }

    if (
      employeeEntity.dateResign &&
      recordDate.toDate() > employeeEntity.dateResign
    ) {
      throw new EmployeeAlreadyResignedException();
    }

    // 2. Check if block exist
    const blockEntity = await this.blockService.findOne({
      name: blockName,
    });

    if (!blockEntity) {
      throw new BlockNotFoundException();
    }

    if (!blockEntity.isActive) {
      throw new BlockNotActiveException();
    }

    // 3. Check if activity setting exist
    const activitySettingEntity =
      await this.activityRecordSettingService.findOne({
        name: activityName,
      });

    if (!activitySettingEntity) {
      throw new ActivityRecordSettingNotFoundException();
    }

    if (
      !activitySettingEntity.isActive ||
      (activitySettingEntity.activateUntil &&
        recordDate.toDate() > activitySettingEntity.activateUntil)
    ) {
      throw new ActivityRecordSettingNotActiveException();
    }

    // 4. Check if current record exist
    const existingRecord = await this.findOne({
      date: recordDate.toDate(),
      activityRecordSetting: { id: activitySettingEntity.id },
      block: { id: blockEntity.id },
      employee: { id: employeeEntity.id },
    });

    if (existingRecord) {
      throw new DuplicateActivityRecordException();
    }

    if (
      (activityName === 'annual leave' &&
        quantity > employeeEntity.annualLeave) ||
      (activityName === 'sick leave' && quantity > employeeEntity.sickLeave)
    ) {
      throw new DeductionExceedLeaveException();
    }

    // 5. Check if total working hour > 8 hour
    const totalEmployeeHours = await this.getEmployeeDayHour(
      recordDate.toDate(),
      employeeEntity,
      true,
    );

    if (
      activitySettingEntity.name.toLowerCase() !== 'public holiday' &&
      totalEmployeeHours + hour > 8
    ) {
      throw new ActivityHourExceedException();
    }

    // 6. Create Record
    let toCreateRate = activitySettingEntity.rate;

    //NOTE: make sure there is daily rate setting
    if (activitySettingEntity.sector.name === 'daily rate') {
      toCreateRate =
        recordDate.month() === 1
          ? Math.round(
              (employeeEntity.basicSalary * 100) / recordDate.daysInMonth(),
            ) /
            100 / //If February, then apportion to lesser days
            8 // For daily rate insertion in hour
          : employeeEntity.dailyRateAmount / 8; // For daily rate insertion in hour
    }

    //NOTE: make sure there is daily rate setting
    if (activitySettingEntity.sector.name === 'unpaid leave') {
      toCreateRate =
        recordDate.month() === 1
          ? -Math.round(
              (employeeEntity.basicSalary * 100) / recordDate.daysInMonth(),
            ) /
            100 / //If February, then apportion to lesser days
            8 // For daily rate insertion in hour
          : -employeeEntity.dailyRateAmount / 8; // For daily rate insertion in hour
    }

    const activityRecordEntity = this.activityRecordReposiory.create({
      employee: employeeEntity,
      activityRecordSetting: activitySettingEntity,
      block: blockEntity,
      quantity,
      hour,
      date,
      remark: remark === null ? undefined : remark,
      rate: toCreateRate,
    });

    const createdItem =
      await this.activityRecordReposiory.save(activityRecordEntity);

    await this.auditLogService.create({
      itemId: createdItem.id,
      newValue: JSON.stringify(createdItem),
      oldValue: null,
      operateType: OperateType.CREATE,
      summaryChanges: 'CREATE activity',
      tableName: 'activityRecord',
      user,
    });

    // 7. To update incentive record if any TODO: check if this needs to be inside queue
    if (activitySettingEntity.incentiveName) {
      await this.incentiveRecordService.createOrUpdateIncentiveByFunction(
        employeeEntity,
        {
          date: recordDate.toDate(),
          incentiveName: activitySettingEntity.incentiveName,
        },
      );
    }

    // 8. For perfect attandance calculation
    await this.incentiveRecordService.createOrUpdateIncentiveByFunction(
      employeeEntity,
      {
        date: recordDate.toDate(),
        incentiveName: IncentiveName.PERFECT_ATTENDANCE,
      },
    );

    // 9. To calculate leave
    if (activityName === 'annual leave') {
      await this.employeeService.updateEmployee(employeeEntity, {
        annualLeave: employeeEntity.annualLeave - quantity,
      });
    }

    if (activityName === 'sick leave') {
      await this.employeeService.updateEmployee(employeeEntity, {
        sickLeave: employeeEntity.sickLeave - quantity,
      });
    }

    // 10. Calculate advance and rest day calculation
    await this.advanceAndRestDayWagesQueue.add(
      'processAdvanceAndRestDayWagesCalculation',
      { employee: employeeEntity, activityDate: recordDate.toDate() },
    );

    return activityRecordEntity;
  }

  // eslint-disable-next-line sonarjs/cognitive-complexity
  async updateActivityRecord(
    activityRecordEntity: ActivityRecordEntity,
    dto: UpdateActivityRecordDto,
    user: UserEntity,
  ): Promise<ActivityRecordEntity> {
    const { hour, remark, quantity } = dto;
    const oldValue = JSON.stringify({ ...activityRecordEntity });
    let summaryChanges = 'UPDATE ';

    // 1. Check if employee exist
    const employeeEntity = await this.employeeService.findOne({
      name: activityRecordEntity.employee.name,
    });

    if (!employeeEntity) {
      throw new EmployeeNotFoundException();
    }

    if (quantity !== undefined) {
      if (activityRecordEntity.activityRecordSetting.name === 'annual leave') {
        await this.employeeService.updateEmployee(employeeEntity, {
          annualLeave:
            employeeEntity.annualLeave +
            activityRecordEntity.quantity -
            quantity,
        });
      }

      if (activityRecordEntity.activityRecordSetting.name === 'sick leave') {
        await this.employeeService.updateEmployee(employeeEntity, {
          sickLeave:
            employeeEntity.sickLeave + activityRecordEntity.quantity - quantity,
        });
      }

      summaryChanges +=
        activityRecordEntity.quantity === quantity
          ? ''
          : `Quantity: ${activityRecordEntity.quantity} -> ${quantity}, `;
      activityRecordEntity.quantity = quantity;
    }

    if (hour !== undefined) {
      // 5. Check if total working hour > 8 hour
      if (hour > 8) {
        throw new ActivityHourExceedException();
      }

      summaryChanges +=
        activityRecordEntity.hour === hour
          ? ''
          : `Hour: ${activityRecordEntity.hour} -> ${hour}, `;
      activityRecordEntity.hour = hour;
    }

    if (remark) {
      summaryChanges +=
        activityRecordEntity.remark === remark
          ? ''
          : `Remark: ${activityRecordEntity.remark} -> ${remark}, `;

      activityRecordEntity.remark = remark;
    }

    const updatedItem =
      await this.activityRecordReposiory.save(activityRecordEntity);

    await this.auditLogService.create({
      itemId: updatedItem.id,
      newValue: JSON.stringify(updatedItem),
      oldValue,
      operateType: OperateType.UPDATE,
      summaryChanges,
      tableName: 'activityRecord',
      user,
    });

    if (activityRecordEntity.activityRecordSetting.incentiveName) {
      await this.incentiveRecordService.createOrUpdateIncentiveByFunction(
        employeeEntity,
        {
          date: activityRecordEntity.date,
          incentiveName:
            activityRecordEntity.activityRecordSetting.incentiveName,
        },
      );
    }

    // For perfect attandance calculation
    await this.incentiveRecordService.createOrUpdateIncentiveByFunction(
      employeeEntity,
      {
        date: activityRecordEntity.date,
        incentiveName: IncentiveName.PERFECT_ATTENDANCE,
      },
    );

    // Calculate advance and rest day calculation
    await this.advanceAndRestDayWagesQueue.add(
      'processAdvanceAndRestDayWagesCalculation',
      { employee: employeeEntity, activityDate: activityRecordEntity.date },
    );

    return activityRecordEntity;
  }

  async deleteActivityRecord(
    activityRecordEntity: ActivityRecordEntity,
    user: UserEntity,
  ): Promise<void> {
    const employeeEntity = await this.employeeService.findOne({
      id: activityRecordEntity.employee.id,
    });

    if (!employeeEntity) {
      throw new EmployeeNotFoundException();
    }

    if (activityRecordEntity.activityRecordSetting.name === 'annual leave') {
      await this.employeeService.updateEmployee(employeeEntity, {
        annualLeave: employeeEntity.annualLeave + activityRecordEntity.quantity,
      });
    }

    if (activityRecordEntity.activityRecordSetting.name === 'sick leave') {
      await this.employeeService.updateEmployee(employeeEntity, {
        sickLeave: employeeEntity.sickLeave + activityRecordEntity.quantity,
      });
    }

    if (activityRecordEntity.activityRecordSetting.incentiveName) {
      await this.incentiveRecordService.deleteIncentiveRecord(
        employeeEntity,
        {
          date: activityRecordEntity.date,
          incentiveName:
            activityRecordEntity.activityRecordSetting.incentiveName,
        },
        user,
      );
    }

    // For perfect attandance calculation
    await this.incentiveRecordService.createOrUpdateIncentiveByFunction(
      employeeEntity,
      {
        date: activityRecordEntity.date,
        incentiveName: IncentiveName.PERFECT_ATTENDANCE,
      },
    );

    await this.auditLogService.create({
      itemId: activityRecordEntity.id,
      newValue: null,
      oldValue: JSON.stringify(activityRecordEntity),
      operateType: OperateType.DELETE,
      summaryChanges: 'DELETE activity',
      tableName: 'activityRecord',
      user,
    });

    await this.activityRecordReposiory.remove(activityRecordEntity);

    // Calculate advance and rest day calculation
    await this.advanceAndRestDayWagesQueue.add(
      'processAdvanceAndRestDayWagesCalculation',
      { employee: employeeEntity, activityDate: activityRecordEntity.date },
    );
  }

  async getEmployeeDayHour(
    date: Date,
    employee: EmployeeEntity,
    shouldExcludePublicHoliday = false,
  ) {
    const queryBuilder = this.activityRecordReposiory
      .createQueryBuilder('activityRecord')
      .select('SUM(activityRecord.hour)', 'hour')
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect(['employee.id'])
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .where('activityRecord.date = :date', { date })
      .andWhere('employee.id = :id', { id: employee.id });

    if (shouldExcludePublicHoliday) {
      const publicHolidayActivitySetting =
        await this.activityRecordSettingService.findOne({
          name: 'Public Holiday',
        });

      if (publicHolidayActivitySetting) {
        queryBuilder.andWhere('setting.id != :settingId', {
          settingId: publicHolidayActivitySetting.id,
        });
      }
    }

    queryBuilder.groupBy('employee.id');

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const returnData = await queryBuilder.getRawOne();

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    return returnData ? (returnData.hour as unknown as number) : 0;
  }

  async getEmployeeIncentiveQuantity(
    date: Date,
    employee: EmployeeEntity,
    incentiveName: IncentiveName,
  ) {
    const queryBuilder = this.activityRecordReposiory
      .createQueryBuilder('activityRecord')
      .select('SUM(activityRecord.quantity)', 'quantity')
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect(['employee.id'])
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .addSelect(['setting.incentiveName'])
      .where('activityRecord.date = :date', { date })
      .andWhere('employee.id = :id', { id: employee.id })
      .andWhere('setting.incentiveName = :incentiveName', { incentiveName })
      .groupBy('employee.id')
      .addGroupBy('setting.incentiveName');

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const returnData = await queryBuilder.getRawOne();

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    return returnData ? (returnData.quantity as unknown as number) : 0;
  }

  async getEmployeeWorkingHour(
    employee: EmployeeEntity,
    options: { startDate: Date; endDate: Date },
  ) {
    const { endDate, startDate } = options;

    const queryBuilder = this.activityRecordReposiory
      .createQueryBuilder('activityRecord')
      .select('SUM(activityRecord.hour)', 'count')
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect(['employee.id'])
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .leftJoin('setting.sector', 'sector')
      .where('activityRecord.date >= :startDate', { startDate })
      .andWhere('activityRecord.date < :endDate', { endDate })
      .andWhere('sector.name != :zeroRate', { zeroRate: 'zero rate' })
      .andWhere('sector.name != :unpaidLeave', { unpaidLeave: 'unpaid leave' })
      .andWhere('employee.id = :id', { id: employee.id })
      .groupBy('employee.id');

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const returnData = await queryBuilder.getRawOne();

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    return returnData ? (Number(returnData.count) as unknown as number) : 0;
  }

  async getEmployeeWorkingHourByReport(
    employee: EmployeeEntity,
    options?: { startDate?: Date; endDate?: Date },
  ) {
    const queryBuilder = this.activityRecordReposiory
      .createQueryBuilder('activityRecord')
      .select('SUM(activityRecord.hour)', 'count')
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect(['employee.id'])
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .leftJoin('setting.sector', 'sector')
      .where('employee.id = :id', { id: employee.id })
      .andWhere('sector.name != :zeroRate', { zeroRate: 'zero rate' })
      .andWhere('sector.name != :unpaidLeave', { unpaidLeave: 'unpaid leave' });

    if (options?.startDate) {
      queryBuilder.andWhere('activityRecord.date >= :startDate', {
        startDate: options.startDate,
      });
    }

    if (options?.endDate) {
      queryBuilder.andWhere('activityRecord.date < :endDate', {
        endDate: options.endDate,
      });
    }

    queryBuilder.groupBy('employee.id');

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const returnData = await queryBuilder.getRawOne();

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    return returnData ? (Number(returnData.count) as unknown as number) : 0;
  }

  async getEmployeeSumRecordByDay(
    employee: EmployeeEntity,
    options?: {
      startDate?: Date;
      endDate?: Date;
    },
  ) {
    const queryBuilder = this.activityRecordReposiory
      .createQueryBuilder('activityRecord')
      .select('SUM(activityRecord.rate * activityRecord.quantity)', 'sumAmount')
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect(['employee.id'])
      .leftJoin('activityRecord.activityRecordSetting', 'setting')
      .leftJoin('setting.sector', 'sector')
      .where('employee.id = :id', { id: employee.id });

    if (options?.startDate) {
      queryBuilder.andWhere('activityRecord.date >= :startDate', {
        startDate: options.startDate,
      });
    }

    if (options?.endDate) {
      queryBuilder.andWhere('activityRecord.date < :endDate', {
        endDate: options.endDate,
      });
    }

    queryBuilder.groupBy('employee.id');

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const returnData = await queryBuilder.getRawOne();

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    return returnData ? (Number(returnData.sumAmount) as unknown as number) : 0;
  }

  async getSummaryActivityByEmployee(
    dto: GetByEmployeeDto,
    employee: EmployeeEntity,
  ) {
    const { monthFrom, monthTo, sectorName, year } = dto;

    const currentYear = year ?? moment.utc().year();

    const queryBuilder = this.activityRecordReposiory
      .createQueryBuilder('activityRecord')
      .select(
        'SUM(ROUND(activityRecord.rate * activityRecord.quantity, 2))',
        'sumAmount',
      )
      .addSelect('SUM(activityRecord.quantity)', 'sumQuantity')
      .leftJoin('activityRecord.employee', 'employee')
      .addSelect('employee.id', 'employeeId')
      .where('employee.id = :id', { id: employee.id });

    let fromDate: Date | undefined;
    let toDate: Date | undefined;

    if (monthFrom) {
      fromDate = moment
        .utc({
          year: currentYear,
          month: monthFrom - 1,
          date: 1,
        })
        .toDate();

      queryBuilder.andWhere('activityRecord.date >= :fromDate', { fromDate });
    }

    if (monthTo) {
      toDate = moment
        .utc({ year: currentYear, month: monthTo - 1, date: 1 })
        .add(1, 'months')
        .toDate();

      queryBuilder.andWhere('activityRecord.date < :toDate', { toDate });
    }

    queryBuilder.groupBy('employee.id');

    if (sectorName) {
      const sectorEntity = await this.sectorService.findOne({
        name: sectorName,
      });

      if (!sectorEntity) {
        throw new SectorNotFoundException();
      }

      queryBuilder
        .leftJoin('activityRecord.activityRecordSetting', 'activitySetting')
        .leftJoin('activitySetting.sector', 'sector')
        .addSelect('sector.name', 'activitySector')
        .andWhere('sector.name = :sectorName', {
          sectorName,
        });

      queryBuilder.addGroupBy('sector.name');
    }

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const sumData = await queryBuilder.getRawOne();
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const count = await this.getEmployeeWorkingHourByReport(employee, {
      startDate: fromDate,
      endDate: toDate,
    });

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const returnData = sumData
      ? {
          ...sumData,
          // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
          sumAmount: Number(sumData.sumAmount),
          // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
          sumQuantity: Number(sumData.sumQuantity),
        }
      : {
          employeeId: employee.id,
          activitySector: sectorName,
          sumAmount: 0,
          sumQuantity: 0,
        };

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    returnData.workingDay = Number(count);

    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
    return returnData;
  }

  async getAdvanceInfoByEmployee(
    dto: GetByEmployeeDto,
    employee: EmployeeEntity,
    adminConfig: AdminConfigEntity,
  ) {
    const { monthFrom, year } = dto;

    const currentYear = year ?? moment.utc().year();

    let startDate = moment.utc().startOf('month').toDate();

    if (monthFrom) {
      startDate = moment
        .utc({
          year: currentYear,
          month: monthFrom - 1,
          date: 1,
        })
        .toDate();
    }

    const endDate = moment
      .utc(startDate)
      .add(adminConfig.advanceLastExecutionDate, 'day')
      .toDate();

    const sumActivityAmount = await this.getEmployeeSumRecordByDay(employee, {
      startDate,
      endDate,
    });
    const sumWorkingHour = await this.getEmployeeWorkingHour(employee, {
      startDate,
      endDate,
    });

    return {
      employeeName: employee.name,
      sumActivityAmount,
      expectedAmount: adminConfig.advanceRequiredWages,
      sumWorkingHour,
      expectedWorkingHour: adminConfig.advanceRequiredDays * 8,
    };
  }

  async getRestDayWagesInfoByEmployee(
    dto: GetByEmployeeDto,
    employee: EmployeeEntity,
    adminConfig: AdminConfigEntity,
  ) {
    const { monthFrom, year } = dto;

    const currentYear = year ?? moment.utc().year();

    let currentStartOfMonth = moment.utc().startOf('month').toDate();

    if (monthFrom) {
      currentStartOfMonth = moment
        .utc({
          year: currentYear,
          month: monthFrom - 1,
          date: 1,
        })
        .toDate();
    }

    const currentEndOfMonth = moment
      .utc(currentStartOfMonth)
      .endOf('month')
      .toDate();

    // 5. Calculate rest day wages
    let loopStartDate = moment
      .utc(currentStartOfMonth)
      .startOf('isoWeek')
      .toDate();
    let loopEndDate = moment
      .utc(loopStartDate)
      .endOf('isoWeek')
      .startOf('day')
      .toDate();

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const toReturnObject: any = {
      employeeName: employee.name,
      expectedWorkingHourPerWeek:
        adminConfig.restDayWagesRequiredDaysInWeek * 8,
    };

    while (loopEndDate <= currentEndOfMonth) {
      // Check the number of employee's working hours
      const workingHours =
        // eslint-disable-next-line no-await-in-loop
        await this.getEmployeeWorkingHour(employee, {
          startDate: loopStartDate,
          endDate: loopEndDate,
        });

      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      toReturnObject[loopEndDate.toLocaleDateString()] = workingHours;

      loopStartDate = moment.utc(loopStartDate).add(7, 'day').toDate();
      loopEndDate = moment.utc(loopEndDate).add(7, 'day').toDate();
    }

    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
    return toReturnObject;
  }
}