diff --git a/backend/src/modules/fuel-requests/fuelRequest.service.ts b/backend/src/modules/fuel-requests/fuelRequest.service.ts index 0820054..b2b7420 100644 --- a/backend/src/modules/fuel-requests/fuelRequest.service.ts +++ b/backend/src/modules/fuel-requests/fuelRequest.service.ts @@ -3,6 +3,10 @@ import { IFuelRequest } from "./fuelRequest.interface"; import { generateRequestNumber } from "../../utils/requestNumber"; +import vehiclesConfigModel from "../vehicles-config/vehiclesConfig.model"; +import anomalyModel from "../anomalies/anomaly.model"; +import { IAnomaly } from "../anomalies/anomaly.interface"; + export class FuelRequestService { //Obtener todas las requisiciones @@ -98,4 +102,209 @@ export class FuelRequestService { return await fuelRequestModel .findByIdAndDelete(id); } + + private async detectAnomalies( + fuelRequest:any + ){ + await this.validateMileage( + fuelRequest + ); + + await this.validateFuelConsumption( + fuelRequest + ); + + await this.validateTankCapacity( + fuelRequest + ); + + await this.validatePreviousFuelRequest( + fuelRequest + ); + } + + private async validateMileage( + data:any + ){ + + //Kilometraje menor que el anterior + if ( + data.currentMileage < + data.previousMileage + ){ + await this.createAnomaly( + data, + "KILOMETRAJE FUERA DE RANGO", + "El kilometraje actual es menor que el anterior" + ); + } + + //Odomentro en mal estado + if(data.currentMileage === data.previousMileage){ + await this.createAnomaly( + data, + "ODOMETRO EN MAL ESTADO", + "El kilometraje no cambió respecto al anterior" + ); + } + + //Diferencia mayor a 500km + if(data.distanceTraveled>500){ + await this.createAnomaly( + data, + "KILOMETRAJE FUERA DE RANGO", + "La distancia recorrida supera los 500 kilometros" + ); + } + } + + + //Validar rendimiento de combustible + private async validateFuelConsumption( + data:any + ){ + //Evitar división entre 0} + if( + data.gallonQuantity <=0 + ){ + return; + } + + //Calcular distancia recorrida + + const distance = + data.currentMileage - data.previousMileage; + + //Calcular rendimiento + + const performance = + + distance/ + data.gallonQuantity; + + //Bajo rendimiento + + if (performance < 10){ + await this.createAnomaly( + data, + "RENDIMIENTO BAJO", + `El vehículo tiene un rendimiento de ${performance.toFixed(2)} km por galón.` + ) + } + } + + //Validar capacidad del tanque + private async validateTankCapacity( + data:any + ){ + const vehicleConfig = await vehiclesConfigModel.findOne({ + vehicleid:data.vehicleId + }); + + //Sino existe configuración no validar + if(!vehicleConfig){ + return; + } + + //Validar capacidad del tanque + + if( + data.gallonQuantity > + vehicleConfig.tankcapacity + ){ + await this.createAnomaly( + data, + "CONSUMO ELEVADO", + `Se solicitaron ${data.gallonQuantity} galones, pero la capacidad del tanque es de ${vehicleConfig.tankcapacity} galones.` + ) + } + } + + // Validar contra la carga anterior + + private async validatePreviousFuelRequest( + data:any + ) { + + // Buscar la última requisición del vehículo + const previousRequest = + await fuelRequestModel + .findOne({ + + vehicleId: data.vehicleId, + + _id: { $ne: data._id } + + }) + .sort({ + createdAt: -1 + }); + + // Si no existe una carga anterior, salir + if (!previousRequest) { + + return; + + } + + // Calcular diferencia de galones + const difference = Math.abs( + + data.gallonQuantity - + + previousRequest.gallonQuantity + + ); + + // Si la diferencia es mayor a 5 galones + if (difference > 5) { + + await this.createAnomaly( + + data, + + "CONSUMO ELEVADO", + + `La carga actual difiere en ${difference} galones respecto a la carga anterior.` + + ); + + } + + } + + + //Crear anomalía + private async createAnomaly( + + fuelRequest: any, + + anomalyType: IAnomaly["anomalyType"], + + description: string + + ) { + + return await anomalyModel.create({ + + vehicleId: fuelRequest.vehicleId, + + employeeId: fuelRequest.employeeId, + + fuelRequestId: fuelRequest._id, + + anomalyType, + + description, + + detectedBy: "SISTEMA", + + status: "ABIERTA", + + comments: + "Anomalía generada automáticamente por el sistema." + + }); + + } } \ No newline at end of file diff --git a/backend/src/modules/reports/report.controller.ts b/backend/src/modules/reports/report.controller.ts new file mode 100644 index 0000000..4efda16 --- /dev/null +++ b/backend/src/modules/reports/report.controller.ts @@ -0,0 +1,252 @@ +import { Request, Response } from "express"; +import { ReportService } from "./report.service"; + +const service = new ReportService(); + +export class ReportController { + + async getSummary( + req:Request, + res:Response + ){ + + try{ + + const summary = await service.getSummary(); + res.json(summary); + + }catch(error){ + res.status(500).json({ + message:"Error al obtener reportes", + error + }); + + } + } + + // Reporte de Fuel Request + async getFuelRequestsReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getFuelRequestReport(); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte de requisiciones.", + error + }); + + } + + } + + + // Reporte de Facturas Externas + async getExternalInvoicesReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getExternalInvoiceReport(); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte de facturas externas.", + error + }); + + } + + } + + // Reporte de LPG + async getLpgReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getLpgReport(); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte de LPG.", + error + }); + + } + + } + + // Reporte de Anomalías + async getAnomaliesReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getAnomaliesReport(); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte de anomalías.", + error + }); + + } + + } + + // Reporte de Lecturas de Bomba + async getPumpReadingsReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getPumpReadingsReport(); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte de lecturas de bomba.", + error + }); + + } + + } + + // Reporte por Vehículo + async getVehicleReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getVehicleReport( + Number(req.params.vehicleId) + ); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte por vehículo.", + error + }); + + } + + } + + // Reporte por Empleado + async getEmployeeReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getEmployeeReport( + Number(req.params.employeeId) + ); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte por empleado.", + error + }); + + } + + } + + // Reporte por Departamento + async getDepartmentReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getDepartmentReport( + Number(req.params.departmentId) + ); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte por departamento.", + error + }); + + } + + } + + // Reporte por Ruta + async getRouteReport( + req: Request, + res: Response + ) { + + try { + + const report = + await service.getRouteReport( + Number(req.params.routeId) + ); + + res.status(200).json(report); + + } catch (error) { + + res.status(500).json({ + message: "Error al generar el reporte por ruta.", + error + }); + + } + + } + +} \ No newline at end of file diff --git a/backend/src/modules/reports/report.routes.ts b/backend/src/modules/reports/report.routes.ts new file mode 100644 index 0000000..7d3da8c --- /dev/null +++ b/backend/src/modules/reports/report.routes.ts @@ -0,0 +1,29 @@ +import { Router } from "express"; +import { ReportController } from "./report.controller"; +import { authMiddleware } from "../auth/auth.middleware"; + +const reportRouter = Router(); +const controller = new ReportController(); + + +reportRouter.get("/summary", authMiddleware, controller.getSummary); + +reportRouter.get("/fuel-request", authMiddleware, controller.getFuelRequestsReport); + +reportRouter.get("/external-invoice", authMiddleware, controller.getExternalInvoicesReport); + +reportRouter.get("/LPG", authMiddleware, controller.getLpgReport); + +reportRouter.get("/anomalies", authMiddleware, controller.getAnomaliesReport); + +reportRouter.get("/pump-readings", authMiddleware, controller.getPumpReadingsReport); + +reportRouter.get("/vehicle/:vehicleId", authMiddleware, controller.getVehicleReport); + +reportRouter.get("/employee/:employeeId", authMiddleware, controller.getEmployeeReport); + +reportRouter.get("/department/:departmentId", authMiddleware, controller.getDepartmentReport); + +reportRouter.get("/route/:routeId", authMiddleware, controller.getRouteReport); + +export default reportRouter; \ No newline at end of file diff --git a/backend/src/modules/reports/report.service.ts b/backend/src/modules/reports/report.service.ts new file mode 100644 index 0000000..91b4ffb --- /dev/null +++ b/backend/src/modules/reports/report.service.ts @@ -0,0 +1,198 @@ +import fuelRequestModel from "../fuel-requests/fuelRequest.model"; +import externalInvoiceModel from "../external-invoices/externalInvoice.model"; +import lpgModel from "../lpg/lpg.model"; +import approvalModel from "../approvals/approval.model"; +import anomalyModel from "../anomalies/anomaly.model"; +import pumpReadingModel from "../pump-reading/pumpReading.model"; + + +export class ReportService{ + + async getSummary(){ + //Total de requisiciones + const fuelRequests = await fuelRequestModel.countDocuments(); + + //Total de facturas externas + const externalInvoices = await externalInvoiceModel.countDocuments(); + + //Total de requisiciones LPG + const lpgRequests = await lpgModel.countDocuments(); + + //Total de galones solicitados + const pendingApprovals = await approvalModel.countDocuments({ + status:"PENDIENTE" + }); + + //Total de anomalías abiertas + const openAnomalies = await anomalyModel.countDocuments({ + status:"ABIERTA" + }) + + //Tootal de lecturas de bomba + const pumpReadings = await pumpReadingModel.countDocuments(); + + const gallons = await fuelRequestModel.aggregate([ + { + $group:{ + _id:null, + totalGallons:{ + $sum: "$gallonQuantity" + } + } + } + ]); + + //Total invertido + const totalCost = await fuelRequestModel.aggregate([ + { + $group:{ + _id:null, + totalAmount:{ + $sum: "$totalAmount" + } + } + + } + ]); + + return{ + fuelRequests, + externalInvoices, + lpgRequests, + pendingApprovals, + openAnomalies, + pumpReadings, + + totalGallons: + gallons.length > 0 + ? gallons[0].totalGallons + :0, + + + totalCost: + totalCost.length > 0 + ? totalCost[0].totalAmount + :0 + + } + } + + async getFuelRequestReport(){ + + return await fuelRequestModel + .find() + .populate("fuelPumpId") + .populate("fuelTypeId") + .populate("createdBy") + .populate("authorizedBy") + .sort({ + createdAt:-1 + }); + } + + async getExternalInvoiceReport(){ + + return await externalInvoiceModel + .find() + .populate("fuelTypeId") + .populate("createdBy") + .populate("authorizedBy") + .sort({ + invoiceDate:-1 + }); + } + + async getLpgReport(){ + + return await lpgModel + .find() + .populate("requestedBy") + .populate("approvedBy") + .sort({ + requestDate:-1 + }); + } + + async getAnomaliesReport(){ + + return await anomalyModel + .find() + .populate("reportedBy") + .populate("resolvedBy") + .sort({ + createdAt:-1 + }); + } + + async getPumpReadingsReport(){ + + return await pumpReadingModel + .find() + .populate("fuelPumpId") + .populate("fuelTypeId") + .populate("createdBy") + .sort({ + readingDate:-1 + }); + } + + async getVehicleReport( + vehicleId:number + ){ + + return await fuelRequestModel + .find({ + vehicleId + }) + .populate("fuelPumpId") + .populate("fuelTypeId") + .sort({ + createdAt:-1 + }); + } + + async getEmployeeReport( + employeeId:number + ){ + + return await fuelRequestModel + .find({ + employeeId + }) + .populate("fuelPumpId") + .populate("fuelTypeId") + .sort({ + createdAt:-1 + }); + } + + async getDepartmentReport( + departmentId:number + ){ + + return await fuelRequestModel + .find({ + departmentId + }) + .populate("fuelPumpId") + .populate("fuelTypeId") + .sort({ + createdAt:-1 + }); + } + + async getRouteReport( + routeId:number + ){ + + return await fuelRequestModel + .find({ + routeId + }) + .populate("fuelPumpId") + .populate("fuelTypeId") + .sort({ + createdAt:-1 + }); + } +} \ No newline at end of file