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, @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, shouldLeftJoin?: boolean, ): Promise { 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> { 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 { 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 { 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 { 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; } }