|
|
@@ -49,7 +49,7 @@ router.get('/', async (req, res) => {
|
|
|
|
|
|
// Create charge (with optional invoice file)
|
|
|
router.post('/', upload.single('invoice'), async (req, res) => {
|
|
|
- const { property_id, category, label, amount, date, year, notes, recoverable } = req.body;
|
|
|
+ const { property_id, category, label, amount, date, year, notes, recoverable_type } = req.body;
|
|
|
if (!property_id || !category || !label || !amount || !date || !year)
|
|
|
return res.status(400).json({ error: 'Champs requis manquants' });
|
|
|
|
|
|
@@ -58,11 +58,12 @@ router.post('/', upload.single('invoice'), async (req, res) => {
|
|
|
|
|
|
const invoice_path = req.file ? req.file.filename : null;
|
|
|
const invoice_name = req.file ? req.file.originalname : null;
|
|
|
+ const rtype = ['recoverable', 'deductible', 'none'].includes(recoverable_type) ? recoverable_type : 'recoverable';
|
|
|
|
|
|
const result = await pool.query(`
|
|
|
- INSERT INTO charges (user_id, property_id, category, label, amount, date, year, invoice_path, invoice_name, notes, recoverable)
|
|
|
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id
|
|
|
- `, [req.userId, property_id, category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null, recoverable === 'false' || recoverable === '0' ? 0 : 1]);
|
|
|
+ INSERT INTO charges (user_id, property_id, category, label, amount, date, year, invoice_path, invoice_name, notes, recoverable, recoverable_type)
|
|
|
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id
|
|
|
+ `, [req.userId, property_id, category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null, rtype === 'recoverable' ? 1 : 0, rtype]);
|
|
|
|
|
|
res.status(201).json({ id: result.rows[0].id });
|
|
|
});
|
|
|
@@ -72,7 +73,7 @@ router.put('/:id', upload.single('invoice'), async (req, res) => {
|
|
|
const charge = (await pool.query('SELECT * FROM charges WHERE id = $1 AND user_id = $2', [req.params.id, req.userId])).rows[0];
|
|
|
if (!charge) return res.status(404).json({ error: 'Charge non trouvée' });
|
|
|
|
|
|
- const { category, label, amount, date, year, notes, recoverable } = req.body;
|
|
|
+ const { category, label, amount, date, year, notes, recoverable_type } = req.body;
|
|
|
|
|
|
let invoice_path = charge.invoice_path;
|
|
|
let invoice_name = charge.invoice_name;
|
|
|
@@ -87,10 +88,12 @@ router.put('/:id', upload.single('invoice'), async (req, res) => {
|
|
|
invoice_name = req.file.originalname;
|
|
|
}
|
|
|
|
|
|
+ const rtype = ['recoverable', 'deductible', 'none'].includes(recoverable_type) ? recoverable_type : 'recoverable';
|
|
|
+
|
|
|
await pool.query(`
|
|
|
- UPDATE charges SET category=$1, label=$2, amount=$3, date=$4, year=$5, invoice_path=$6, invoice_name=$7, notes=$8, recoverable=$9
|
|
|
- WHERE id=$10
|
|
|
- `, [category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null, recoverable === 'false' || recoverable === '0' ? 0 : 1, req.params.id]);
|
|
|
+ UPDATE charges SET category=$1, label=$2, amount=$3, date=$4, year=$5, invoice_path=$6, invoice_name=$7, notes=$8, recoverable=$9, recoverable_type=$10
|
|
|
+ WHERE id=$11
|
|
|
+ `, [category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null, rtype === 'recoverable' ? 1 : 0, rtype, req.params.id]);
|
|
|
|
|
|
res.json({ success: true });
|
|
|
});
|
|
|
@@ -132,29 +135,36 @@ router.get('/bilan/:property_id/:year', async (req, res) => {
|
|
|
// Total recoverable charges for this property/year (for tenant billing)
|
|
|
const totalChargesRow = (await pool.query(`
|
|
|
SELECT SUM(amount) as total, COUNT(*) as count
|
|
|
- FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable = 1
|
|
|
+ FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type = 'recoverable'
|
|
|
+ `, [property_id, year, req.userId])).rows[0];
|
|
|
+
|
|
|
+ // Total deductible (landlord fiscal charges, for info)
|
|
|
+ const totalDeductibleRow = (await pool.query(`
|
|
|
+ SELECT SUM(amount) as total
|
|
|
+ FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type = 'deductible'
|
|
|
`, [property_id, year, req.userId])).rows[0];
|
|
|
|
|
|
- // Total non-recoverable (landlord's own charges, for info)
|
|
|
+ // Total non-recoverable / non-deductible (landlord's own charges, for info)
|
|
|
const totalNonRecovRow = (await pool.query(`
|
|
|
- SELECT SUM(amount) as total, COUNT(*) as count
|
|
|
- FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable = 0
|
|
|
+ SELECT SUM(amount) as total
|
|
|
+ FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type = 'none'
|
|
|
`, [property_id, year, req.userId])).rows[0];
|
|
|
|
|
|
- const totalChargesReelles = totalChargesRow?.total || 0;
|
|
|
- const totalNonRecoverable = totalNonRecovRow?.total || 0;
|
|
|
+ const totalChargesReelles = parseFloat(totalChargesRow?.total || 0);
|
|
|
+ const totalDeductible = parseFloat(totalDeductibleRow?.total || 0);
|
|
|
+ const totalNonRecoverable = parseFloat(totalNonRecovRow?.total || 0);
|
|
|
|
|
|
// Charges by category (recoverable only)
|
|
|
const chargesByCategory = (await pool.query(`
|
|
|
SELECT category, SUM(amount) as total
|
|
|
- FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable = 1
|
|
|
+ FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type = 'recoverable'
|
|
|
GROUP BY category ORDER BY total DESC
|
|
|
`, [property_id, year, req.userId])).rows;
|
|
|
|
|
|
// Non-recoverable by category (for info)
|
|
|
const chargesNonRecovByCategory = (await pool.query(`
|
|
|
SELECT category, SUM(amount) as total
|
|
|
- FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable = 0
|
|
|
+ FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type != 'recoverable'
|
|
|
GROUP BY category ORDER BY total DESC
|
|
|
`, [property_id, year, req.userId])).rows;
|
|
|
|
|
|
@@ -204,6 +214,7 @@ router.get('/bilan/:property_id/:year', async (req, res) => {
|
|
|
property: prop,
|
|
|
year: parseInt(year),
|
|
|
total_charges_reelles: Math.round(totalChargesReelles * 100) / 100,
|
|
|
+ total_deductible: Math.round(totalDeductible * 100) / 100,
|
|
|
total_non_recoverable: Math.round(totalNonRecoverable * 100) / 100,
|
|
|
charges_count: totalChargesRow?.count || 0,
|
|
|
charges_by_category: chargesByCategory,
|
|
|
@@ -251,9 +262,9 @@ router.get('/bilan/:property_id/:year/pdf', async (req, res) => {
|
|
|
|
|
|
const totalChargesRow = (await pool.query(`
|
|
|
SELECT SUM(amount) as total, COUNT(*) as count FROM charges
|
|
|
- WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable = 1
|
|
|
+ WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type = 'recoverable'
|
|
|
`, [property_id, year, req.userId])).rows[0];
|
|
|
- const totalChargesReelles = totalChargesRow?.total || 0;
|
|
|
+ const totalChargesReelles = parseFloat(totalChargesRow?.total || 0);
|
|
|
|
|
|
const chargesList = (await pool.query(`
|
|
|
SELECT * FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3
|
|
|
@@ -262,7 +273,7 @@ router.get('/bilan/:property_id/:year/pdf', async (req, res) => {
|
|
|
|
|
|
const chargesByCategory = (await pool.query(`
|
|
|
SELECT category, SUM(amount) as total FROM charges
|
|
|
- WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable = 1
|
|
|
+ WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type = 'recoverable'
|
|
|
GROUP BY category ORDER BY total DESC
|
|
|
`, [property_id, year, req.userId])).rows;
|
|
|
|
|
|
@@ -489,7 +500,7 @@ router.post('/bilan/:property_id/:year/close', async (req, res) => {
|
|
|
|
|
|
// Recompute bilan (recoverable only)
|
|
|
const totalRow = (await pool.query(
|
|
|
- 'SELECT SUM(amount) as total FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable = 1',
|
|
|
+ `SELECT SUM(amount) as total FROM charges WHERE property_id = $1 AND year = $2 AND user_id = $3 AND recoverable_type = 'recoverable'`,
|
|
|
[property_id, year, req.userId]
|
|
|
)).rows[0];
|
|
|
const totalCharges = totalRow?.total || 0;
|