const { pool } = require('../db'); const normalizeText = (value) => String(value || '').replace(/\s+/g, ' ').trim(); const normalizeNumber = (value) => { if (value === '' || value === null || value === undefined) return null; const number = Number(String(value).replace(',', '.')); return Number.isFinite(number) && number > 0 ? number : null; }; const mapReceipt = (row) => ({ id: row.id, category: row.category, product: row.product, quantity: Number(row.quantity), unit: row.unit, supplier: row.supplier || '', invoice: row.invoice || '', notes: row.notes || '', status: row.status, createdAt: row.created_at, updatedAt: row.updated_at, approvedAt: row.approved_at }); const mapLot = (row) => ({ id: row.id, receiptId: row.receipt_id, category: row.category, product: row.product, quantity: Number(row.quantity), unit: row.unit, supplier: row.supplier || '', invoice: row.invoice || '', status: row.status, createdAt: row.created_at, updatedAt: row.updated_at }); const mapMovement = (row) => ({ id: row.id, receiptId: row.receipt_id, lotId: row.lot_id, type: row.type, category: row.category, product: row.product, quantity: Number(row.quantity), unit: row.unit, reason: row.reason || '', createdAt: row.created_at }); const createValidationError = (message) => { const error = new Error(message); error.statusCode = 400; return error; }; const listReceipts = async () => { const result = await pool.query(` SELECT id, category, product, quantity, unit, supplier, invoice, notes, status, created_at, updated_at, approved_at FROM supply_receipts ORDER BY created_at DESC, id DESC `); return result.rows.map(mapReceipt); }; const listLots = async () => { const result = await pool.query(` SELECT id, receipt_id, category, product, quantity, unit, supplier, invoice, status, created_at, updated_at FROM supply_stock_lots WHERE status = 'active' ORDER BY created_at DESC, id DESC `); return result.rows.map(mapLot); }; const listMovements = async () => { const result = await pool.query(` SELECT id, receipt_id, lot_id, type, category, product, quantity, unit, reason, created_at FROM supply_movements ORDER BY created_at DESC, id DESC `); return result.rows.map(mapMovement); }; const buildStats = (receipts, lots) => { const totalQuantityKg = lots.reduce((total, lot) => ( lot.unit === 'kg' ? total + lot.quantity : total ), 0); return { totalQuantityKg, activeLots: lots.length, rolls: lots.filter(lot => lot.unit === 'rolos').reduce((total, lot) => total + lot.quantity, 0), alerts: 0, pendingReceipts: receipts.filter(receipt => receipt.status === 'pending').length, approvedReceipts: receipts.filter(receipt => receipt.status === 'approved').length }; }; const getSupplySummary = async () => { const [receipts, lots, movements] = await Promise.all([ listReceipts(), listLots(), listMovements() ]); return { receipts, lots, movements, stats: buildStats(receipts, lots) }; }; const createReceipt = async (payload) => { const category = normalizeText(payload.category); const product = normalizeText(payload.product); const quantity = normalizeNumber(payload.quantity); const unit = normalizeText(payload.unit) || 'kg'; if (!category) throw createValidationError('Categoria é obrigatória.'); if (!product) throw createValidationError('Produto ou material é obrigatório.'); if (!quantity) throw createValidationError('Quantidade deve ser maior que zero.'); const result = await pool.query(` INSERT INTO supply_receipts ( category, product, quantity, unit, supplier, invoice, notes, status, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, 'pending', CURRENT_TIMESTAMP) RETURNING id, category, product, quantity, unit, supplier, invoice, notes, status, created_at, updated_at, approved_at `, [ category, product, quantity, unit, normalizeText(payload.supplier) || null, normalizeText(payload.invoice) || null, normalizeText(payload.notes) || null ]); return mapReceipt(result.rows[0]); }; const approveReceipt = async (id) => { const client = await pool.connect(); try { await client.query('BEGIN'); const receiptResult = await client.query(` SELECT id, category, product, quantity, unit, supplier, invoice, notes, status, created_at, updated_at, approved_at FROM supply_receipts WHERE id = $1 FOR UPDATE `, [id]); if (!receiptResult.rowCount) { throw createValidationError('Recebimento não encontrado.'); } const receipt = receiptResult.rows[0]; if (receipt.status === 'approved') { await client.query('COMMIT'); return mapReceipt(receipt); } const updatedReceiptResult = await client.query(` UPDATE supply_receipts SET status = 'approved', approved_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING id, category, product, quantity, unit, supplier, invoice, notes, status, created_at, updated_at, approved_at `, [id]); const lotResult = await client.query(` INSERT INTO supply_stock_lots ( receipt_id, category, product, quantity, unit, supplier, invoice, status, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, 'active', CURRENT_TIMESTAMP) RETURNING id `, [ receipt.id, receipt.category, receipt.product, receipt.quantity, receipt.unit, receipt.supplier, receipt.invoice ]); await client.query(` INSERT INTO supply_movements ( receipt_id, lot_id, type, category, product, quantity, unit, reason ) VALUES ($1, $2, 'receipt', $3, $4, $5, $6, $7) `, [ receipt.id, lotResult.rows[0].id, receipt.category, receipt.product, receipt.quantity, receipt.unit, `Recebimento aprovado${receipt.invoice ? ` · NF ${receipt.invoice}` : ''}` ]); await client.query('COMMIT'); return mapReceipt(updatedReceiptResult.rows[0]); } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } }; const deleteReceipt = async (id) => { const client = await pool.connect(); try { await client.query('BEGIN'); await client.query('DELETE FROM supply_movements WHERE receipt_id = $1', [id]); await client.query('DELETE FROM supply_stock_lots WHERE receipt_id = $1', [id]); await client.query('DELETE FROM supply_receipts WHERE id = $1', [id]); await client.query('COMMIT'); } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } }; module.exports = { approveReceipt, createReceipt, deleteReceipt, getSupplySummary, listLots, listMovements, listReceipts };