import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, Repository } from 'typeorm'; import { MaintenanceRepository } from './maintenance.repository'; import { MaintenanceSchedule, MaintenanceStatus } from './entities/maintenance-schedule.entity'; import { MaintenanceCost } from './entities/maintenance-cost.entity'; import { Vehicle, VehicleAvailability, VehicleStatus } from '../vehicles/entities/vehicle.entity'; import { CreateMaintenanceScheduleDto, CreateMaintenanceCostDto, UpdateMaintenanceScheduleDto } from './dto/create-maintenance.dto'; @Injectable() export class MaintenanceService { constructor( private readonly maintenanceRepository: MaintenanceRepository, @InjectRepository(MaintenanceSchedule) private readonly scheduleRepository: Repository, @InjectRepository(MaintenanceCost) private readonly costRepository: Repository, // Vehicle isn't registered in this module's TypeOrmModule.forFeature, so we // reach it through the global DataSource rather than @InjectRepository. private readonly dataSource: DataSource, ) {} /** * Reflect a maintenance schedule's lifecycle on the target vehicle. A vehicle * under maintenance is taken out of service (MAINTENANCE + BUSY); once the * maintenance completes or is cancelled it returns to service (ACTIVE + FREE). * Only the vehicle's status/availability columns are written here. The * assignment-side reject (first-mile/last-mile refusing MAINTENANCE vehicles) * lives in those excluded mile modules, not here. */ private async setVehicleMaintenanceState( vehicleId: string, underMaintenance: boolean, ): Promise { await this.dataSource.getRepository(Vehicle).update(vehicleId, { status: underMaintenance ? VehicleStatus.MAINTENANCE : VehicleStatus.ACTIVE, availability: underMaintenance ? VehicleAvailability.BUSY : VehicleAvailability.FREE, }); } async scheduleMaintenanceAsync(dto: CreateMaintenanceScheduleDto): Promise { const schedule = this.scheduleRepository.create({ ...dto, scheduledDate: new Date(dto.scheduledDate), nextDueDate: dto.nextDueDate ? new Date(dto.nextDueDate) : undefined, }); const saved = await this.scheduleRepository.save(schedule); // Scheduling maintenance takes the vehicle out of the available pool. await this.setVehicleMaintenanceState(saved.vehicleId, true); return saved; } async recordMaintenanceCost(dto: CreateMaintenanceCostDto): Promise { const cost = this.costRepository.create({ ...dto, incurredDate: new Date(dto.incurredDate), }); return this.costRepository.save(cost); } async updateMaintenanceSchedule( id: string, dto: UpdateMaintenanceScheduleDto, ): Promise { await this.scheduleRepository.update(id, { ...dto, completedDate: dto.completedDate ? new Date(dto.completedDate) : undefined, }); const updated = await this.scheduleRepository.findOneBy({ id }); // Keep the vehicle's status/availability in step with the schedule status. if (updated && dto.status) { if ( dto.status === MaintenanceStatus.COMPLETED || dto.status === MaintenanceStatus.CANCELLED ) { // Maintenance finished/aborted → vehicle back in service. await this.setVehicleMaintenanceState(updated.vehicleId, false); } else if (dto.status === MaintenanceStatus.IN_PROGRESS) { // Maintenance started → keep the vehicle out of service. await this.setVehicleMaintenanceState(updated.vehicleId, true); } } return updated!; } async getUpcomingMaintenance(vehicleId: string) { return this.maintenanceRepository.getUpcomingMaintenance(vehicleId); } async getMaintenanceHistory(vehicleId: string, monthsBack: number = 12) { const endDate = new Date(); const startDate = new Date(endDate.getFullYear(), endDate.getMonth() - monthsBack, 1); return this.maintenanceRepository.getMaintenanceCosts(vehicleId, startDate, endDate); } async getFleetMaintenanceStats(monthsBack: number = 12) { const endDate = new Date(); const startDate = new Date(endDate.getFullYear(), endDate.getMonth() - monthsBack, 1); const costs = await this.costRepository .createQueryBuilder('cost') .where('cost.incurredDate BETWEEN :startDate AND :endDate', { startDate, endDate }) .getMany(); const totalCost = costs.reduce((sum: number, c: MaintenanceCost) => sum + Number(c.costAmount), 0); return { totalCost, numberOfMaintenanceItems: costs.length, averageCostPerMaintenance: costs.length > 0 ? totalCost / costs.length : 0, costByType: this.groupCostsByType(costs), }; } async getVehicleMaintenanceStats(vehicleId: string, monthsBack: number = 12) { const endDate = new Date(); const startDate = new Date(endDate.getFullYear(), endDate.getMonth() - monthsBack, 1); const costs = await this.maintenanceRepository.getMaintenanceCosts(vehicleId, startDate, endDate); const totalCost = costs.reduce((sum: number, c: MaintenanceCost) => sum + Number(c.costAmount), 0); return { vehicleId, totalCost, numberOfMaintenanceItems: costs.length, averageCostPerMaintenance: costs.length > 0 ? totalCost / costs.length : 0, costByType: this.groupCostsByType(costs), }; } private groupCostsByType(costs: MaintenanceCost[]) { const grouped: Record = {}; costs.forEach((c) => { if (!grouped[c.costType]) grouped[c.costType] = 0; grouped[c.costType] += Number(c.costAmount); }); return grouped; } }