From 0d8ef99e8100c0e6e5d3321d87de5eb9ea9d935e Mon Sep 17 00:00:00 2001 From: karol Date: Tue, 23 Jun 2026 15:27:56 +0000 Subject: [PATCH] CRUD y validaciones de modulo de facturas externas --- backend/src/app.ts | 2 + .../modules/approvals/approval.controller.ts | 14 +- .../externalInvoice.controller.ts | 328 ++++++++++++++++++ .../externalInvoice.interface.ts | 35 ++ .../externalInvoice.model.ts | 108 ++++++ .../externalInvoice.routes.ts | 47 +++ .../externalInvoice.service.ts | 172 +++++++++ .../fuel-requests/fuelRequest.model.ts | 2 +- 8 files changed, 701 insertions(+), 7 deletions(-) create mode 100644 backend/src/modules/external-invoices/externalInvoice.controller.ts create mode 100644 backend/src/modules/external-invoices/externalInvoice.interface.ts create mode 100644 backend/src/modules/external-invoices/externalInvoice.model.ts create mode 100644 backend/src/modules/external-invoices/externalInvoice.routes.ts create mode 100644 backend/src/modules/external-invoices/externalInvoice.service.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index 7d7fb2f..eb5e3b2 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -11,6 +11,7 @@ import vehicleConfigRouter from "./modules/vehicles-config/vehiclesConfigs.route import vAssigRouter from "./modules/vehicle-assignments/vehicleassig.routes"; import fuelRequestRouter from "./modules/fuel-requests/fuelRequest.routes"; import approvalrouter from "./modules/approvals/approval.routes"; +import invoicerouter from "./modules/external-invoices/externalInvoice.routes"; @@ -30,6 +31,7 @@ app.use("/api/vconfig", vehicleConfigRouter); app.use("/api/vAssig", vAssigRouter); app.use("/api/fuelRequest", fuelRequestRouter); app.use("/api/approvals", approvalrouter); +app.use("/api/exterinvoice", invoicerouter); app.use(express.urlencoded({ diff --git a/backend/src/modules/approvals/approval.controller.ts b/backend/src/modules/approvals/approval.controller.ts index 02cf51a..13c92b1 100644 --- a/backend/src/modules/approvals/approval.controller.ts +++ b/backend/src/modules/approvals/approval.controller.ts @@ -73,13 +73,15 @@ export class ApprovalController { ); } - catch (error) { + catch (error:any) { - res.status(500).json({ - message: - "Error al obtener aprobación", - error - }); + console.error(error); + + res.status(500).json({ + message: "Error al aprobar solicitud", + error: error.message, + stack: error.stack + }); } diff --git a/backend/src/modules/external-invoices/externalInvoice.controller.ts b/backend/src/modules/external-invoices/externalInvoice.controller.ts new file mode 100644 index 0000000..5cc70a6 --- /dev/null +++ b/backend/src/modules/external-invoices/externalInvoice.controller.ts @@ -0,0 +1,328 @@ +import { Request, Response } from "express"; + +import { ExternalInvoiceService } from "./externalInvoice.service"; + +const service = new ExternalInvoiceService(); + +export class ExternalInvoiceController { + + //Obtener todas las facturas + + async getAll( + req: Request, + res: Response + ) { + + try { + + const invoices = + await service.getAll(); + + res.json(invoices); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al obtener facturas", + error + }); + + } + + } + + //Obtener factura por ID + + async getById( + req: Request, + res: Response + ) { + + try { + + const invoice = + await service.getById( + req.params.id as string + ); + + if (!invoice) { + + return res.status(404).json({ + message: + "Factura no encontrada" + }); + + } + + res.json(invoice); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al obtener factura", + error + }); + + } + + } + + //Crear factura + + async create( + req: Request, + res: Response + ) { + + try { + + const invoice = + await service.create( + req.body + ); + + res.status(201).json( + invoice + ); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al crear factura", + error + }); + + } + + } + + //Actualizar factura + + async update( + req: Request, + res: Response + ) { + + try { + + const invoice = + await service.update( + + req.params.id as string, + req.body + + ); + + if (!invoice) { + + return res.status(404).json({ + message: + "Factura no encontrada" + }); + + } + + res.json(invoice); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al actualizar factura", + error + }); + + } + + } + + //Aprobar factura + + async approve( + req: Request, + res: Response + ) { + + try { + + const invoice = + await service.approve( + + req.params.id as string, + req.body.approvedBy + + ); + + if (!invoice) { + + return res.status(404).json({ + message: + "Factura no encontrada" + }); + + } + + res.json(invoice); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al aprobar factura", + error + }); + + } + + } + + //Rechazar factura + + async reject( + req: Request, + res: Response + ) { + + try { + + const invoice = + await service.reject( + + req.params.id as string, + req.body.approvedBy, + req.body.comments + + ); + + if (!invoice) { + + return res.status(404).json({ + message: + "Factura no encontrada" + }); + + } + + res.json(invoice); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al rechazar factura", + error + }); + + } + + } + + //Buscar por estado + + async getByStatus( + req: Request, + res: Response + ) { + + try { + + const invoices = + await service.getByStatus( + req.params.status as string + ); + + res.json(invoices); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al buscar facturas", + error + }); + + } + + } + + //Buscar por zona + + async getByZone( + req: Request, + res: Response + ) { + + try { + + const invoices = + await service.getByZone( + req.params.zone as string + ); + + res.json(invoices); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al buscar facturas", + error + }); + + } + + } + + //Eliminar factura + + async delete( + req: Request, + res: Response + ) { + + try { + + const invoice = + await service.delete( + req.params.id as string + ); + + if (!invoice) { + + return res.status(404).json({ + message: + "Factura no encontrada" + }); + + } + + res.json({ + message: + "Factura eliminada correctamente" + }); + + } + catch (error) { + + res.status(500).json({ + message: + "Error al eliminar factura", + error + }); + + } + + } + +} \ No newline at end of file diff --git a/backend/src/modules/external-invoices/externalInvoice.interface.ts b/backend/src/modules/external-invoices/externalInvoice.interface.ts new file mode 100644 index 0000000..2ba840f --- /dev/null +++ b/backend/src/modules/external-invoices/externalInvoice.interface.ts @@ -0,0 +1,35 @@ +import { ObjectId } from "mongoose"; + +export interface IExternalInvoice { + + invoiceNumber:string; + + zone:string; + + employeeId:number; + + vehicleId:number; + + routeId?:number; + + departmentId:number; + + fuelTypeId:ObjectId; + + invoiceDate:Date; + + gallons:number; + + pricePerGallon:number; + + totalAmount?:number; + + createdBy:ObjectId; + + approvedBy?:ObjectId; + + comments?:string; + + status:string; + +} \ No newline at end of file diff --git a/backend/src/modules/external-invoices/externalInvoice.model.ts b/backend/src/modules/external-invoices/externalInvoice.model.ts new file mode 100644 index 0000000..e42094f --- /dev/null +++ b/backend/src/modules/external-invoices/externalInvoice.model.ts @@ -0,0 +1,108 @@ +import mongoose from "mongoose"; + +const externalInvoiceSchema = new mongoose.Schema({ + + invoiceNumber:{ + type:String, + required:true, + unique:true + }, + + zone:{ + type:String, + required:true, + enum:[ + "Ocotepeque", + "Gracias", + "Santa Bárbara" + ] + }, + + employeeId:{ + type:Number, + required:true + }, + + vehicleId:{ + type:Number, + required:true + }, + + routeId:{ + type:Number, + }, + + fuelTypeId:{ + type:mongoose.Schema.Types.ObjectId, + ref:"FuelType", + required:true + }, + + invoiceDate:{ + type:Date, + required:true + }, + + gallons:{ + type:Number, + required:true + }, + + pricePerGallon:{ + type:Number, + required:true + }, + + totalAmount:{ + type:Number, + default:0 + }, + + createdBy:{ + type:mongoose.Schema.Types.ObjectId, + ref:"User", + required:true + }, + + approvedBy:{ + type:mongoose.Schema.Types.ObjectId, + ref:"User" + }, + + comments:{ + type:String + }, + + status:{ + type:String, + enum:[ + "PENDIENTE", + "APROBADO", + "RECHAZADO" + ], + default:"PENDIENTE" + } + +}, +{ + timestamps:true, + versionKey:false +}); + +//Calcular total antes de guardar + +externalInvoiceSchema.pre( + "save", + async function () { + + this.totalAmount = + this.gallons * + this.pricePerGallon; + + } +); + +export default mongoose.model( + "ExternalInvoice", + externalInvoiceSchema +); \ No newline at end of file diff --git a/backend/src/modules/external-invoices/externalInvoice.routes.ts b/backend/src/modules/external-invoices/externalInvoice.routes.ts new file mode 100644 index 0000000..3deb76b --- /dev/null +++ b/backend/src/modules/external-invoices/externalInvoice.routes.ts @@ -0,0 +1,47 @@ +import { Router } from "express"; + +import { ExternalInvoiceController } from "./externalInvoice.controller"; + +import { authMiddleware } from "../auth/auth.middleware"; + +const invoicerouter = Router(); + +const controller = new ExternalInvoiceController(); + +//Obtener todas las facturas + +invoicerouter.get("/", authMiddleware, controller.getAll); + +//Obtener factura por ID + +invoicerouter.get("/:id", authMiddleware, controller.getById); + +//Crear factura + +invoicerouter.post("/", authMiddleware, controller.create); + +//Actualizar factura + +invoicerouter.put("/:id", authMiddleware, controller.update); + +//Aprobar factura + +invoicerouter.put("/:id/approve", authMiddleware, controller.approve); + +//Rechazar factura + +invoicerouter.put("/:id/reject", authMiddleware, controller.reject); + +//Buscar por estado + +invoicerouter.get("/status/:status", authMiddleware, controller.getByStatus); + +//Buscar por zona + +invoicerouter.get("/zone/:zone", authMiddleware, controller.getByZone); + +//Eliminar factura + +invoicerouter.delete("/:id", authMiddleware, controller.delete); + +export default invoicerouter; \ No newline at end of file diff --git a/backend/src/modules/external-invoices/externalInvoice.service.ts b/backend/src/modules/external-invoices/externalInvoice.service.ts new file mode 100644 index 0000000..d73683b --- /dev/null +++ b/backend/src/modules/external-invoices/externalInvoice.service.ts @@ -0,0 +1,172 @@ +import ExternalInvoiceModel from "./externalInvoice.model"; + +import { IExternalInvoice } from "./externalInvoice.interface"; + +export class ExternalInvoiceService { + + //Crear factura externa + + async create( + data: IExternalInvoice + ) { + + // Calcular monto total + data.totalAmount = + data.gallons * + data.pricePerGallon; + const invoice = + new ExternalInvoiceModel(data); + + return await invoice.save(); + + } + + //Obtener todas las facturas + + async getAll() { + + return await ExternalInvoiceModel + .find() + .populate("fuelTypeId") + .populate("createdBy") + .populate("approvedBy") + .sort({ + createdAt: -1 + }); + + } + + // Obtener factura por ID + + async getById( + id: string + ) { + + return await ExternalInvoiceModel + .findById(id) + .populate("fuelTypeId") + .populate("createdBy") + .populate("approvedBy"); + + } + + //Actualizar factura + + async update( + id: string, + data: Partial + ) { + + // Recalcular total si cambian datos + if ( + data.gallons && + data.pricePerGallon + ) { + + data.totalAmount = + data.gallons * + data.pricePerGallon; + + } + + return await ExternalInvoiceModel + .findByIdAndUpdate( + id, + data, + { + new: true + } + ); + + } + + //Aprobar factura + + async approve( + id: string, + approvedBy: string + ) { + + return await ExternalInvoiceModel + .findByIdAndUpdate( + + id, + + { + status: "APROBADO", + approvedBy + }, + + { + new: true + } + + ); + + } + + //Rechazar factura + + async reject( + id: string, + approvedBy: string, + comments?: string + ) { + + return await ExternalInvoiceModel + .findByIdAndUpdate( + + id, + + { + status: "RECHAZADO", + approvedBy, + comments + }, + + { + new: true + } + + ); + + } + + // Buscar por estado + + async getByStatus( + status: string + ) { + + return await ExternalInvoiceModel + .find({ + status + } as any); + + } + + // Buscar por zona + + async getByZone( + zone: string + ) { + + return await ExternalInvoiceModel + .find({ + zone + } as any); + + } + + //Eliminar factura + + async delete( + id: string + ) { + + return await ExternalInvoiceModel + .findByIdAndDelete(id); + + } + +} \ No newline at end of file diff --git a/backend/src/modules/fuel-requests/fuelRequest.model.ts b/backend/src/modules/fuel-requests/fuelRequest.model.ts index 104887f..fd26306 100644 --- a/backend/src/modules/fuel-requests/fuelRequest.model.ts +++ b/backend/src/modules/fuel-requests/fuelRequest.model.ts @@ -3,7 +3,7 @@ import mongoose from "mongoose"; const fuelRequestSchema = new mongoose.Schema({ requestNumber:{ - type:Number, + type:String, required:true, unique:true },