penisularhr / src / modules / vehicle / vehicle-record.service.ts
vehicle-record.service.ts
Raw
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import moment from 'moment';
import { type FindOptionsWhere, Repository } from 'typeorm';

import { type PageDto } from '../../common/dto/page.dto';
import {
  ActivityRecordSettingNotActiveException,
  ActivityRecordSettingNotFoundException,
  BlockNotFoundException,
  DuplicateVehicleRecordException,
  VehicleIsNotActiveException,
  VehicleNotFoundException,
  VehicleRecordExceed8HourException,
} from '../../exceptions';
import { ActivityRecordSettingService } from '../activity-record/activity-record-setting.service';
import { BlockService } from '../block/block.service';
import { type GetVehicleDto } from '../report/dtos/get-report-vehicle.dto';
import { type CreateVehicleRecordDto } from './dtos/create-vehicle-record.dto';
import { type VehicleRecordPageOptionsDto } from './dtos/get-vehicle-record-page.dto';
import { type UpdateVehicleRecordDto } from './dtos/update-vehicle-record.dto';
import { type VehicleRecordDto } from './dtos/vehicle-record.dto';
import { VehicleNameService } from './vehicle-name.service';
import { VehicleRecordEntity } from './vehicle-record.entity';

@Injectable()
export class VehicleRecordService {
  constructor(
    @InjectRepository(VehicleRecordEntity)
    private vehicleRecordRepository: Repository<VehicleRecordEntity>,
    private blockService: BlockService,
    private vehicleNameService: VehicleNameService,
    private activitySettingService: ActivityRecordSettingService,
  ) {}

  async findOne(
    findData?: FindOptionsWhere<VehicleRecordEntity>,
  ): Promise<VehicleRecordEntity | null> {
    const queryBuilder =
      this.vehicleRecordRepository.createQueryBuilder('vehicleRecord');

    queryBuilder
      .leftJoin('vehicleRecord.block', 'block')
      .addSelect(['block.name'])
      .leftJoin('vehicleRecord.vehicleName', 'vehicleName')
      .addSelect(['vehicleName.name']);

    if (findData) {
      queryBuilder.where(findData);
    }

    return queryBuilder.getOne();
  }

  async findMany(
    pageOptionsDto: VehicleRecordPageOptionsDto,
  ): Promise<PageDto<VehicleRecordDto>> {
    const { order, blockName, date, vehicleName } = pageOptionsDto;

    const queryBuilder =
      this.vehicleRecordRepository.createQueryBuilder('vehicleRecord');

    queryBuilder
      .leftJoin('vehicleRecord.block', 'block')
      .addSelect(['block.name'])
      .leftJoin('vehicleRecord.vehicleName', 'vehicleName')
      .addSelect(['vehicleName.name'])
      .leftJoin('vehicleRecord.activitySetting', 'activitySetting')
      .addSelect(['activitySetting.name']);

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

    if (vehicleName) {
      queryBuilder.andWhere('vehicleName.name ILIKE :vehicleName', {
        vehicleName: `%${vehicleName}%`,
      });
    }

    if (date) {
      queryBuilder.andWhere('vehicleRecord.date = :date', { date });
    }

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

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

    return items.toPageDto(pageMetaDto);
  }

  async findReportMany(dto: GetVehicleDto) {
    const {
      blockName,
      vehicleName,
      monthFrom,
      monthTo,
      order,
      year,
      activityName,
    } = dto;

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

    const queryBuilder =
      this.vehicleRecordRepository.createQueryBuilder('vehicleRecord');
    queryBuilder
      .leftJoin('vehicleRecord.block', 'block')
      .addSelect(['block.name'])
      .leftJoin('vehicleRecord.vehicleName', 'vehicleName')
      .addSelect(['vehicleName.name'])
      .leftJoin('vehicleRecord.activitySetting', 'activitySetting')
      .addSelect(['activitySetting.name']);

    if (blockName) {
      const blockEntity = await this.blockService.findOne({
        name: blockName,
      });

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

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

    if (vehicleName) {
      const vehicleNameEntity = await this.vehicleNameService.findOne({
        name: vehicleName,
      });

      if (!vehicleNameEntity) {
        throw new VehicleNotFoundException();
      }

      queryBuilder.andWhere('vehicleName.name = :vehicleName', {
        vehicleName,
      });
    }

    if (activityName) {
      const activityNameEntity = await this.activitySettingService.findOne({
        name: activityName,
      });

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

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

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

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

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

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

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

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

    const response = await queryBuilder.getMany();

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

  async createVehicle(
    dto: CreateVehicleRecordDto,
  ): Promise<VehicleRecordEntity> {
    const { activityName, blockName, date, hour, vehicleName } = dto;

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

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

    // 2. Check if vehicle exist
    const vehicleNameEntity = await this.vehicleNameService.findOne({
      name: vehicleName,
    });

    if (!vehicleNameEntity) {
      throw new VehicleNotFoundException();
    }

    if (!vehicleNameEntity.isActive) {
      throw new VehicleIsNotActiveException();
    }

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

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

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

    if (!activitySetting.isActive) {
      throw new ActivityRecordSettingNotActiveException();
    }

    if (
      activitySetting.activateUntil &&
      recordDate.toDate() > activitySetting.activateUntil
    ) {
      throw new ActivityRecordSettingNotActiveException();
    }

    const existingEntity = await this.findOne({
      block: { id: blockEntity.id },
      date: recordDate.toDate(),
      vehicleName: { id: vehicleNameEntity.id },
      activitySetting: { id: activitySetting.id },
    });

    if (existingEntity) {
      throw new DuplicateVehicleRecordException();
    }

    const sumHour = await this.getHourSum(recordDate.toDate(), vehicleName);

    if (sumHour + hour > 8) {
      throw new VehicleRecordExceed8HourException();
    }

    const vehicleEntity = this.vehicleRecordRepository.create({
      ...dto,
      block: blockEntity,
      vehicleName: vehicleNameEntity,
      activitySetting,
    });

    await this.vehicleRecordRepository.save(vehicleEntity);

    return vehicleEntity;
  }

  // eslint-disable-next-line sonarjs/cognitive-complexity
  async updateVehicle(
    vehicleRecordEntity: VehicleRecordEntity,
    dto: UpdateVehicleRecordDto,
  ): Promise<VehicleRecordEntity> {
    const { blockName, hour, vehicleName, activityName } = dto;

    if (vehicleName) {
      const vehicleNameEntity = await this.vehicleNameService.findOne({
        name: vehicleName,
      });

      if (!vehicleNameEntity) {
        throw new VehicleNotFoundException();
      }

      if (!vehicleNameEntity.isActive) {
        throw new VehicleIsNotActiveException();
      }

      vehicleRecordEntity.vehicleName = vehicleNameEntity;
    }

    if (blockName) {
      const blockEntity = await this.blockService.findOne({
        name: blockName,
      });

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

      vehicleRecordEntity.block = blockEntity;
    }

    if (hour !== undefined) {
      const sumHour = await this.getHourSum(
        vehicleRecordEntity.date,
        vehicleRecordEntity.vehicleName.name,
      );

      if (sumHour - vehicleRecordEntity.hour + hour > 8) {
        throw new VehicleRecordExceed8HourException();
      }

      vehicleRecordEntity.hour = hour;
    }

    if (activityName) {
      const activitySetting = await this.activitySettingService.findOne({
        name: activityName,
      });

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

      if (!activitySetting.isActive) {
        throw new ActivityRecordSettingNotActiveException();
      }

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

      if (
        activitySetting.activateUntil &&
        recordDate.toDate() > activitySetting.activateUntil
      ) {
        throw new ActivityRecordSettingNotActiveException();
      }

      vehicleRecordEntity.activitySetting = activitySetting;
    }

    await this.vehicleRecordRepository.save(vehicleRecordEntity);

    return vehicleRecordEntity;
  }

  async deleteVehicle(vehicleRecordEntity: VehicleRecordEntity): Promise<void> {
    await this.vehicleRecordRepository.remove(vehicleRecordEntity);
  }

  async getHourSum(date: Date, vehicleName: string): Promise<number> {
    const queryBuilder =
      this.vehicleRecordRepository.createQueryBuilder('vehicleRecord');

    queryBuilder
      .leftJoin('vehicleRecord.vehicleName', 'vehicleName')
      .addSelect(['vehicleName.name']);

    queryBuilder
      .select('SUM(vehicleRecord.hour)', 'hour')
      .where('vehicleRecord.date = :date', { date })
      .andWhere('vehicleName.name = :vehicleName', {
        vehicleName,
      });

    // 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.hour) as unknown as number) : 0;
  }
}