269 lines
No EOL
7.1 KiB
JavaScript
269 lines
No EOL
7.1 KiB
JavaScript
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; |