const express = require('express'); const router = express.Router(); const { transactionRepository, transactionLineRepository } = require('../repositories'); /** * GET /api/plugins/financials/transactions * Get all transactions for a site */ router.get('/', async (req, res) => { try { const { site_id, type, status, start_date, end_date } = req.query; if (!site_id) { return res.status(400).json({ success: false, error: 'site_id is required' }); } let transactions; // Filter by type if provided if (type) { transactions = await transactionRepository.findByType(site_id, type); } // Filter by date range if provided else if (start_date && end_date) { transactions = await transactionRepository.findByDateRange(site_id, start_date, end_date); } // Filter by status if provided else if (status) { transactions = await transactionRepository.findByStatus(site_id, status); } // Get all transactions else { transactions = await transactionRepository.findBySiteId(site_id); } res.json({ success: true, data: transactions, message: 'Transactions retrieved successfully' }); } catch (error) { console.error('Failed to get transactions:', error); res.status(500).json({ success: false, error: 'Failed to retrieve transactions', message: error.message }); } }); /** * GET /api/plugins/financials/transactions/:id * Get transaction by ID */ router.get('/:id', async (req, res) => { try { const { id } = req.params; const { site_id } = req.query; if (!site_id) { return res.status(400).json({ success: false, error: 'site_id is required' }); } const transaction = await transactionRepository.findById(id); if (!transaction) { return res.status(404).json({ success: false, error: 'Transaction not found' }); } // Verify transaction belongs to site if (transaction.site_id !== site_id) { return res.status(403).json({ success: false, error: 'Access denied' }); } // Get transaction lines for double-entry view const lines = await transactionLineRepository.findByTransactionId(id); res.json({ success: true, data: { ...transaction, lines }, message: 'Transaction details retrieved successfully' }); } catch (error) { console.error('Failed to get transaction:', error); res.status(500).json({ success: false, error: 'Failed to retrieve transaction', message: error.message }); } }); /** * POST /api/plugins/financials/transactions * Create a new transaction with double-entry validation */ router.post('/', async (req, res) => { try { const { site_id, transaction_date, reference_number, description, transaction_type, amount, currency, status, created_by, lines } = req.body; // Validate required fields if (!site_id || !transaction_date || !description || !transaction_type || amount === undefined) { return res.status(400).json({ success: false, error: 'Missing required fields: site_id, transaction_date, description, transaction_type, amount' }); } // If lines provided, validate double-entry balance if (lines && Array.isArray(lines)) { const totalDebits = lines.reduce((sum, line) => sum + parseFloat(line.debit_amount || 0), 0); const totalCredits = lines.reduce((sum, line) => sum + parseFloat(line.credit_amount || 0), 0); if (Math.abs(totalDebits - totalCredits) > 0.01) { // Allow small floating point differences return res.status(400).json({ success: false, error: 'Debits must equal credits', message: `Total debits: ${totalDebits}, Total credits: ${totalCredits}` }); } } // Create transaction const transaction = await transactionRepository.createTransaction({ site_id, transaction_date, reference_number, description, transaction_type, amount, currency: currency || 'USD', status: status || 'pending', created_by: created_by || req.user?.id || 'system' }); // Create transaction lines if provided if (lines && Array.isArray(lines)) { for (const line of lines) { await transactionLineRepository.create({ transaction_id: transaction.id, account_id: line.account_id, debit_amount: line.debit_amount || 0, credit_amount: line.credit_amount || 0, description: line.description, created_at: new Date().toISOString() }); } } // Get the created lines to return with transaction const createdLines = await transactionLineRepository.findByTransactionId(transaction.id); res.status(201).json({ success: true, data: { ...transaction, lines: createdLines }, message: 'Transaction created successfully' }); } catch (error) { console.error('Failed to create transaction:', error); res.status(500).json({ success: false, error: 'Failed to create transaction', message: error.message }); } }); /** * PUT /api/plugins/financials/transactions/:id/approve * Approve a transaction */ router.put('/:id/approve', async (req, res) => { try { const { id } = req.params; const { approved_by } = req.body; if (!approved_by) { return res.status(400).json({ success: false, error: 'approved_by is required' }); } const transaction = await transactionRepository.approveTransaction(id, approved_by); res.json({ success: true, data: transaction, message: 'Transaction approved successfully' }); } catch (error) { console.error('Failed to approve transaction:', error); res.status(500).json({ success: false, error: 'Failed to approve transaction', message: error.message }); } }); /** * DELETE /api/plugins/financials/transactions/:id * Delete a transaction */ router.delete('/:id', async (req, res) => { try { const { id } = req.params; const { site_id } = req.query; if (!site_id) { return res.status(400).json({ success: false, error: 'site_id is required' }); } // Get transaction to verify it exists and belongs to site const transaction = await transactionRepository.findById(id); if (!transaction) { return res.status(404).json({ success: false, error: 'Transaction not found' }); } if (transaction.site_id !== site_id) { return res.status(403).json({ success: false, error: 'Access denied' }); } // Delete transaction (lines will be deleted via CASCADE) await transactionRepository.deleteById(id); res.json({ success: true, message: 'Transaction deleted successfully' }); } catch (error) { console.error('Failed to delete transaction:', error); res.status(500).json({ success: false, error: 'Failed to delete transaction', message: error.message }); } }); module.exports = router;