VENDEDOR EN PEDIDO Y EN ADMINISTRADOR
This commit is contained in:
+136
-17
@@ -379,6 +379,23 @@ async function requireProductosManagement(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
async function requirePedidosManagement(req, res, next) {
|
||||
const session = await getSessionUser(req);
|
||||
if (!session) return res.status(401).json({ error: 'No autorizado' });
|
||||
|
||||
const user = await getUserData(session.usuario);
|
||||
if (!user) return res.status(401).json({ error: 'No autorizado' });
|
||||
|
||||
const esAdmin = user.rol === 'admin';
|
||||
const tienePermiso = (user.permisos || []).includes('pedidos');
|
||||
|
||||
if (!esAdmin && !tienePermiso) {
|
||||
return res.status(403).json({ error: 'No tienes permiso para gestionar pedidos' });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
}
|
||||
|
||||
// Permite acceder si es admin O tiene el permiso 'descargas'
|
||||
async function requireDescargasAccess(req, res, next) {
|
||||
const session = await getSessionUser(req);
|
||||
@@ -1889,10 +1906,104 @@ app.post('/api/descargas/fotos', requireDescargasAccess, async (req, res) => {
|
||||
});
|
||||
|
||||
// ════════════════════════════════
|
||||
// PEDIDOS — generar PDF con pedido
|
||||
// VENDEDORES — gestión (admin / permiso pedidos)
|
||||
// ════════════════════════════════
|
||||
const VENDEDORES_FILE = 'vendedores.json';
|
||||
const PEDIDOS_META_FILE = 'pedidos-meta.json';
|
||||
|
||||
async function getVendedores() {
|
||||
try {
|
||||
return await readJson(VENDEDORES_FILE);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getPedidosMeta() {
|
||||
try {
|
||||
return await readJson(PEDIDOS_META_FILE);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/api/vendedores', async (req, res) => {
|
||||
const vendedores = await getVendedores();
|
||||
res.json(vendedores);
|
||||
});
|
||||
|
||||
app.post('/api/vendedores', requirePedidosManagement, async (req, res) => {
|
||||
const { nombre } = req.body || {};
|
||||
if (!nombre || !nombre.trim()) {
|
||||
return res.status(400).json({ error: 'El nombre del vendedor es obligatorio' });
|
||||
}
|
||||
|
||||
const vendedores = await getVendedores();
|
||||
const id = String(Date.now());
|
||||
vendedores.push({ id, nombre: nombre.trim() });
|
||||
await writeJson(VENDEDORES_FILE, vendedores);
|
||||
res.json(vendedores);
|
||||
});
|
||||
|
||||
app.delete('/api/vendedores/:id', requirePedidosManagement, async (req, res) => {
|
||||
let vendedores = await getVendedores();
|
||||
vendedores = vendedores.filter(v => v.id !== req.params.id);
|
||||
await writeJson(VENDEDORES_FILE, vendedores);
|
||||
res.json(vendedores);
|
||||
});
|
||||
|
||||
// ════════════════════════════════
|
||||
// PEDIDOS — generar PDF, listar y eliminar metadatos
|
||||
// ════════════════════════════════
|
||||
|
||||
// Listar pedidos (admin / permiso pedidos)
|
||||
app.get('/api/pedidos', requirePedidosManagement, async (req, res) => {
|
||||
const { vendedor } = req.query;
|
||||
let pedidos = await getPedidosMeta();
|
||||
|
||||
if (vendedor) {
|
||||
pedidos = pedidos.filter(p => p.vendedor === vendedor);
|
||||
}
|
||||
|
||||
res.json(pedidos);
|
||||
});
|
||||
|
||||
// Eliminar pedido (admin / permiso pedidos)
|
||||
app.delete('/api/pedidos/:filename', requirePedidosManagement, async (req, res) => {
|
||||
const { filename } = req.params;
|
||||
|
||||
// Validar que el filename sea seguro (solo letras, números, guiones, puntos)
|
||||
if (!/^[\w.-]+\.pdf$/.test(filename)) {
|
||||
return res.status(400).json({ error: 'Nombre de archivo inválido' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Eliminar archivo PDF
|
||||
const filepath = path.join(PEDIDOS_DIR, filename);
|
||||
try {
|
||||
await fs.unlink(filepath);
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
// Si no existe el archivo, igual seguimos para limpiar metadatos
|
||||
}
|
||||
|
||||
// Eliminar metadatos
|
||||
const pedidosMeta = await getPedidosMeta();
|
||||
const nuevosMeta = pedidosMeta.filter(p => p.filename !== filename);
|
||||
if (nuevosMeta.length === pedidosMeta.length) {
|
||||
return res.status(404).json({ error: 'Pedido no encontrado' });
|
||||
}
|
||||
await writeJson(PEDIDOS_META_FILE, nuevosMeta);
|
||||
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
console.error('Delete pedido error:', err);
|
||||
res.status(500).json({ error: 'Error al eliminar el pedido: ' + err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
const { items, cliente } = req.body || {};
|
||||
const { items, cliente, vendedor } = req.body || {};
|
||||
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
return res.status(400).json({ error: 'El pedido debe tener al menos un producto' });
|
||||
@@ -1905,7 +2016,6 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
const filename = `pedido-${Date.now()}.pdf`;
|
||||
const filepath = path.join(PEDIDOS_DIR, filename);
|
||||
|
||||
// Asegurar que la carpeta existe
|
||||
await fs.mkdir(PEDIDOS_DIR, { recursive: true });
|
||||
|
||||
const doc = new PDFDocument({
|
||||
@@ -1925,7 +2035,6 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
const MARGIN_X = 50;
|
||||
const pageH = doc.page.height;
|
||||
|
||||
// === ENCABEZADO ===
|
||||
doc.fontSize(36).fillColor('#8b5cf6').font('Helvetica-Bold')
|
||||
.text('∞ Infinity', MARGIN_X, 50, { width: pageW - 2 * MARGIN_X });
|
||||
|
||||
@@ -1942,18 +2051,23 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
doc.fontSize(10).fillColor('#8b8ba7').font('Helvetica')
|
||||
.text(fechaStr, MARGIN_X, 150, { width: pageW - 2 * MARGIN_X });
|
||||
|
||||
// === DATOS DEL CLIENTE ===
|
||||
doc.fontSize(14).fillColor('#1a1a2e').font('Helvetica-Bold')
|
||||
.text('Datos del cliente', MARGIN_X, 185, { width: pageW - 2 * MARGIN_X });
|
||||
if (vendedor) {
|
||||
doc.fontSize(10).fillColor('#8b5cf6').font('Helvetica-Bold')
|
||||
.text(`Vendedor: ${vendedor}`, MARGIN_X, 168, { width: pageW - 2 * MARGIN_X });
|
||||
}
|
||||
|
||||
doc.fontSize(14).fillColor('#1a1a2e').font('Helvetica-Bold')
|
||||
.text('Datos del cliente', MARGIN_X, vendedor ? 192 : 185, { width: pageW - 2 * MARGIN_X });
|
||||
|
||||
const clientY0 = vendedor ? 217 : 210;
|
||||
const clientFields = [
|
||||
['Nombre', cliente.nombre],
|
||||
['Ciudad', cliente.ciudad],
|
||||
['Nombre', cliente.nombre],
|
||||
['Ciudad', cliente.ciudad],
|
||||
['Teléfono', cliente.telefono],
|
||||
['Dirección', cliente.direccion]
|
||||
];
|
||||
|
||||
let clientY = 210;
|
||||
let clientY = clientY0;
|
||||
doc.fontSize(11).fillColor('#4a4a68').font('Helvetica');
|
||||
for (const [label, value] of clientFields) {
|
||||
doc.text(`${label}: `, MARGIN_X, clientY, { continued: true });
|
||||
@@ -1961,7 +2075,6 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
clientY += 18;
|
||||
}
|
||||
|
||||
// === TABLA DE PRODUCTOS ===
|
||||
let tableY = clientY + 20;
|
||||
doc.moveTo(MARGIN_X, tableY).lineTo(pageW - MARGIN_X, tableY)
|
||||
.strokeColor('#e8e6f0').lineWidth(0.5).stroke();
|
||||
@@ -1980,8 +2093,6 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
doc.text('Precio', colPrecio, tableY, { align: 'right', width: 60 });
|
||||
doc.text('Total', colSubtotal, tableY, { align: 'right', width: 60 });
|
||||
|
||||
const headerY = tableY;
|
||||
|
||||
let totalGeneral = 0;
|
||||
for (const item of items) {
|
||||
const subtotal = (item.precio || 0) * (item.cantidad || 1);
|
||||
@@ -2005,7 +2116,6 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
.text(`$${subtotal.toLocaleString('es-CO')}`, colSubtotal, tableY, { align: 'right', width: 60 });
|
||||
}
|
||||
|
||||
// Línea final
|
||||
tableY += 20;
|
||||
doc.moveTo(MARGIN_X, tableY).lineTo(pageW - MARGIN_X, tableY)
|
||||
.strokeColor('#e8e6f0').lineWidth(0.5).stroke();
|
||||
@@ -2016,7 +2126,6 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
doc.fontSize(16).fillColor('#8b5cf6').font('Helvetica-Bold')
|
||||
.text(` $${totalGeneral.toLocaleString('es-CO')}`, { width: pageW - 2 * MARGIN_X });
|
||||
|
||||
// === FOOTER con número de página ===
|
||||
const range = doc.bufferedPageRange();
|
||||
for (let i = range.start; i < range.start + range.count; i++) {
|
||||
doc.switchToPage(i);
|
||||
@@ -2027,13 +2136,23 @@ app.post('/api/pedidos/generar-pdf', async (req, res) => {
|
||||
|
||||
doc.end();
|
||||
|
||||
// Esperar a que termine de escribir
|
||||
await new Promise((resolve, reject) => {
|
||||
writeStream.on('finish', resolve);
|
||||
writeStream.on('error', reject);
|
||||
});
|
||||
|
||||
// Enviar el archivo
|
||||
// Guardar metadatos del pedido
|
||||
const pedidosMeta = await getPedidosMeta();
|
||||
pedidosMeta.push({
|
||||
filename,
|
||||
fecha: new Date().toISOString(),
|
||||
cliente,
|
||||
items: items.map(i => ({ ref: i.ref, nombre: i.nombre, precio: i.precio, cantidad: i.cantidad })),
|
||||
vendedor: vendedor || null,
|
||||
total: totalGeneral
|
||||
});
|
||||
await writeJson(PEDIDOS_META_FILE, pedidosMeta);
|
||||
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
res.sendFile(filepath);
|
||||
|
||||
Reference in New Issue
Block a user