Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions .env.example

This file was deleted.

36 changes: 34 additions & 2 deletions src/controllers/InvoicesController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,48 @@ import prisma from "../config/prisma.js";
class InvoicesController {

findAll = async (req, res) => {
const invoices = await this.getInvoices(req);
res.json(invoices);
}

getInvoices = async (req) => {
const { startYear, endYear, startMonth, endMonth } = req.body;

const dateFilters = {};

if (startYear && startMonth && startMonth >= 1 && startMonth <= 12) {
const startDate = new Date(`${startYear}-${String(startMonth).padStart(2, '0')}-01T00:00:00Z`);
if (!isNaN(startDate.getTime())) {
dateFilters.gte = startDate;
} else {
return "Invalid Date";
}
}

if (endYear && endMonth && endMonth >= 1 && endMonth <= 12) {
const endDate = new Date(`${(endMonth === 12 ? endYear + 1 : endYear)}-${String((endMonth % 12) + 1).padStart(2, '0')}-01T00:00:00Z`);
endDate.setTime(endDate.getTime() - 1);
if (!isNaN(endDate.getTime())) {
dateFilters.lt = endDate;
} else {
return "Invalid Date";
}
}

const invoices = await prisma.invoice.findMany({
where: {
createdAt: dateFilters,
},
include: {
order: {
include: {
user: true,
}
}
}
})
res.json(invoices);
});

return invoices;
}

findById = async (req, res) => {
Expand Down
90 changes: 90 additions & 0 deletions src/controllers/ReportController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import StatsService from "../services/StatsService.js";
import InvoicesController from "./InvoicesController.js";

class ReportController {

constructor() {
this.statsService = new StatsService();
this.invoicesController = new InvoicesController();
}

salesReport = async (req, res) => {
const { startYear, endYear, startMonth, endMonth } = req.body;

const orders = await this.statsService.orders(startYear, startMonth, endYear, endMonth, undefined);
const revenue = await this.statsService.revenue(startYear, startMonth, endYear, endMonth, undefined);

const totalOrders = orders.totalOrders;
const totalRevenue = revenue.totalRevenue;
const totalSales = totalOrders;

const summary = [
{ métrica: 'Total de Pedidos', valor: totalOrders },
{ métrica: 'Total de Ventas', valor: totalSales },
{ métrica: 'Ingresos Generados', valor: Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP' }).format(totalRevenue) },
];

const formattedOrders = orders.orders.map(order => ({
id: order.id,
usuario: order.user.firstName,
email: order.user.email,
total: Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP' }).format(order.total),
fecha: `${order.createdAt.getDate()}/${order.createdAt.getMonth() + 1}/${order.createdAt.getFullYear()}`,
}));

const formattedProducts = revenue.orderItems.map(orderItem => ({
nombre: orderItem.product_sku.product.name,
talla: orderItem.product_sku.size_attribute.value,
color: orderItem.product_sku.color_attribute.value,
cantidad: orderItem.quantity,
total: Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP' }).format(orderItem.quantity * orderItem.price),
}));

const csvData = [];

csvData.push(['Reporte de ventas ZAFNAT']);
csvData.push([]);
csvData.push(['Métrica', 'Valor']);
summary.forEach(row => csvData.push([row.métrica, row.valor]));
csvData.push([]);
csvData.push(['Detalles de Órdenes']);
csvData.push(['ID', 'Usuario', 'Email', 'Total', 'Fecha']);
formattedOrders.forEach(order => csvData.push([order.id, order.usuario, order.email, order.total, order.fecha]));
csvData.push([]);
csvData.push(['Detalles de Productos']);
csvData.push(['Nombre', 'Talla', 'Color', 'Cantidad Vendida', 'Total de Ingresos']);
formattedProducts.forEach(product => csvData.push([product.nombre, product.talla, product.color, product.cantidad, product.total]));

const csv = csvData.map(row => row.join(';')).join('\n');
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="sales-report.csv"');
res.send(csv);
};

invoicesReport = async (req, res) => {
const invoices = await this.invoicesController.getInvoices(req);
const formattedInvoices = invoices.map(invoice => ({
id: invoice.id,
transaction_id: invoice.transaction_id,
client: invoice.order.user.firstName,
total: Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP' }).format(invoice.amount),
status: invoice.status,
date: invoice.createdAt,
}));

const csvData = [];

csvData.push(['Reporte de facturas ZaFNat']);
csvData.push([]);
csvData.push(['Detalles de Facturas']);
csvData.push(['ID', 'Numero de transaccion', 'Cliente', 'Total', 'Estado de pago', 'Fecha']);
formattedInvoices.forEach(invoice => csvData.push([invoice.id, invoice.transaction_id, invoice.client, invoice.total, invoice.status, invoice.date]));
const csv = csvData.map(row => row.join(';')).join('\n');
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="sales-report.csv"');
res.send(csv);
};

}

export default ReportController;
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import attributesRouter from "./routes/AttributeRouter.js";
import recomendacionesRouter from "./routes/RecomendacionesRouter.js";
import orderRouter from "./routes/OrderRouter.js";
import statsRouter from "./routes/StatsRouter.js";
import reportRouter from "./routes/ReportRouter.js";
import { report } from "process";
import invoiceRouter from "./routes/InvoiceRouter.js";

const app = express();
Expand Down Expand Up @@ -47,6 +49,7 @@ app.use("/api/cart-product", cartProductRouter);
app.use("/api/recomendaciones", recomendacionesRouter);
app.use("/api/orders", orderRouter);
app.use("/api/stats", statsRouter);
app.use("/api/reports", reportRouter);
app.use("/api/invoices", invoiceRouter);

// New routes
Expand Down
6 changes: 4 additions & 2 deletions src/routes/InvoiceRouter.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import express from "express";
import InvoicesController from "../controllers/InvoicesController.js";
import verifyToken from '../middlewares/verifyToken.js';
import checkPermission from '../middlewares/rbac.js';

const router = express.Router();

const invoicesController = new InvoicesController();

router.get("/", invoicesController.findAll);
router.get("/:id", invoicesController.findById);
router.get("/", verifyToken, checkPermission("ADMIN"), invoicesController.findAll);
router.get("/:id", verifyToken, checkPermission("ADMIN"), invoicesController.findById);

export default router;
12 changes: 12 additions & 0 deletions src/routes/ReportRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ReportController from '../controllers/ReportController.js';
import express from 'express';
import verifyToken from '../middlewares/verifyToken.js';
import checkPermission from '../middlewares/rbac.js';

const router = express.Router();
const reportController = new ReportController();

router.get('/sales', verifyToken, checkPermission("ADMIN"), reportController.salesReport);
router.get('/invoices', verifyToken, checkPermission("ADMIN"), reportController.invoicesReport);

export default router;
2 changes: 1 addition & 1 deletion src/routes/StatsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const router = express.Router();

const statsController = new StatsController();

router.get("/sales", verifyToken, checkPermission("ADMIN"), statsController.salesStats);
router.get("/sales", statsController.salesStats);
router.get("/products", verifyToken, checkPermission("ADMIN"), statsController.productsStats);
router.get("/users", verifyToken, checkPermission("ADMIN"), statsController.usersStats);

Expand Down
100 changes: 68 additions & 32 deletions src/services/StatsService.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { get } from "http";
import prisma from "../config/prisma.js";

class StatsService {
Expand Down Expand Up @@ -25,22 +26,7 @@ class StatsService {
}
}

const orders = await prisma.order.findMany({
where: {
createdAt: dateFilters,
items: {
some: {
product_sku_id: {
in: productIds.length > 0 ? productIds : undefined,
},
},
},
},
select: {
createdAt: true,
},
});

const orders = await this.getOrders(dateFilters, productIds);

const monthlyOrderCount = orders.reduce((months, order) => {
const monthKey = `${order.createdAt.getFullYear()}-${String(order.createdAt.getMonth() + 1).padStart(2, '0')}`;
Expand All @@ -53,24 +39,49 @@ class StatsService {
return months;
}, {});


const monthlyOrderArray = Object.entries(monthlyOrderCount).map(([month, count]) => ({
month,
count,
}));


monthlyOrderArray.sort((a, b) => a.month.localeCompare(b.month));


const totalOrders = monthlyOrderArray.reduce((total, monthData) => total + monthData.count, 0);

return {
totalOrders,
monthlyOrders: monthlyOrderArray,
orders,
};
};

getOrders = async (dateFilters, productIds = []) => {
const orders = await prisma.order.findMany({
where: {
createdAt: dateFilters,
items: {
some: {
product_sku_id: {
in: productIds.length > 0 ? productIds : undefined,
},
},
},
},
select: {
id: true,
total: true,
createdAt: true,
user: {
select: {
firstName: true,
email: true,
}
}
},
});
return orders;
};

revenue = async (startYear, startMonth, endYear, endMonth, productIds = []) => {

const dateFilters = {};
Expand All @@ -94,19 +105,7 @@ class StatsService {
}
}

const orderItems = await prisma.orderItem.findMany({
where: {
createdAt: dateFilters,
product_sku_id: {
in: productIds.length > 0 ? productIds : undefined,
},
},
select: {
price: true,
quantity: true,
createdAt: true,
},
});
const orderItems = await this.getOrderItems(dateFilters, productIds);

const totalRevenue = orderItems.reduce((total, item) => {
return total + item.price * item.quantity;
Expand All @@ -133,9 +132,46 @@ class StatsService {
return {
totalRevenue,
monthlyRevenue: monthlyRevenueArray,
orderItems,
};
};

getOrderItems = async (dateFilters, productIds = []) => {
const orderItems = await prisma.orderItem.findMany({
where: {
createdAt: dateFilters,
product_sku_id: {
in: productIds.length > 0 ? productIds : undefined,
},
},
select: {
price: true,
quantity: true,
createdAt: true,
product_sku: {
select: {
product: {
select: {
name: true,
}
},
size_attribute: {
select: {
value: true,
}
},
color_attribute: {
select: {
value: true,
}
},
},
}
},
});
return orderItems;
};

soldProducts = async () => {
const soldProducts = await prisma.orderItem.aggregate({
_sum: {
Expand Down