CRUD y validaciones de modulo de facturas externas

This commit is contained in:
2026-06-23 15:27:56 +00:00
parent 8aefb2575d
commit 0d8ef99e81
8 changed files with 701 additions and 7 deletions

View File

@ -11,6 +11,7 @@ import vehicleConfigRouter from "./modules/vehicles-config/vehiclesConfigs.route
import vAssigRouter from "./modules/vehicle-assignments/vehicleassig.routes"; import vAssigRouter from "./modules/vehicle-assignments/vehicleassig.routes";
import fuelRequestRouter from "./modules/fuel-requests/fuelRequest.routes"; import fuelRequestRouter from "./modules/fuel-requests/fuelRequest.routes";
import approvalrouter from "./modules/approvals/approval.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/vAssig", vAssigRouter);
app.use("/api/fuelRequest", fuelRequestRouter); app.use("/api/fuelRequest", fuelRequestRouter);
app.use("/api/approvals", approvalrouter); app.use("/api/approvals", approvalrouter);
app.use("/api/exterinvoice", invoicerouter);
app.use(express.urlencoded({ app.use(express.urlencoded({

View File

@ -73,13 +73,15 @@ export class ApprovalController {
); );
} }
catch (error) { catch (error:any) {
res.status(500).json({ console.error(error);
message:
"Error al obtener aprobación", res.status(500).json({
error message: "Error al aprobar solicitud",
}); error: error.message,
stack: error.stack
});
} }

View File

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

View File

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

View File

@ -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
);

View File

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

View File

@ -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<IExternalInvoice>
) {
// 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);
}
}

View File

@ -3,7 +3,7 @@ import mongoose from "mongoose";
const fuelRequestSchema = new mongoose.Schema({ const fuelRequestSchema = new mongoose.Schema({
requestNumber:{ requestNumber:{
type:Number, type:String,
required:true, required:true,
unique:true unique:true
}, },