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; } }