|
@@ -49,7 +49,7 @@ router.get('/', (req, res) => {
|
|
|
|
|
|
|
|
// Create charge (with optional invoice file)
|
|
// Create charge (with optional invoice file)
|
|
|
router.post('/', upload.single('invoice'), (req, res) => {
|
|
router.post('/', upload.single('invoice'), (req, res) => {
|
|
|
- const { property_id, category, label, amount, date, year, notes } = req.body;
|
|
|
|
|
|
|
+ const { property_id, category, label, amount, date, year, notes, recoverable } = req.body;
|
|
|
if (!property_id || !category || !label || !amount || !date || !year)
|
|
if (!property_id || !category || !label || !amount || !date || !year)
|
|
|
return res.status(400).json({ error: 'Champs requis manquants' });
|
|
return res.status(400).json({ error: 'Champs requis manquants' });
|
|
|
|
|
|
|
@@ -60,9 +60,9 @@ router.post('/', upload.single('invoice'), (req, res) => {
|
|
|
const invoice_name = req.file ? req.file.originalname : null;
|
|
const invoice_name = req.file ? req.file.originalname : null;
|
|
|
|
|
|
|
|
const result = db.prepare(`
|
|
const result = db.prepare(`
|
|
|
- INSERT INTO charges (user_id, property_id, category, label, amount, date, year, invoice_path, invoice_name, notes)
|
|
|
|
|
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
- `).run(req.userId, property_id, category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null);
|
|
|
|
|
|
|
+ INSERT INTO charges (user_id, property_id, category, label, amount, date, year, invoice_path, invoice_name, notes, recoverable)
|
|
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
+ `).run(req.userId, property_id, category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null, recoverable === 'false' || recoverable === '0' ? 0 : 1);
|
|
|
|
|
|
|
|
res.status(201).json({ id: result.lastInsertRowid });
|
|
res.status(201).json({ id: result.lastInsertRowid });
|
|
|
});
|
|
});
|
|
@@ -72,7 +72,7 @@ router.put('/:id', upload.single('invoice'), (req, res) => {
|
|
|
const charge = db.prepare('SELECT * FROM charges WHERE id = ? AND user_id = ?').get(req.params.id, req.userId);
|
|
const charge = db.prepare('SELECT * FROM charges WHERE id = ? AND user_id = ?').get(req.params.id, req.userId);
|
|
|
if (!charge) return res.status(404).json({ error: 'Charge non trouvée' });
|
|
if (!charge) return res.status(404).json({ error: 'Charge non trouvée' });
|
|
|
|
|
|
|
|
- const { category, label, amount, date, year, notes } = req.body;
|
|
|
|
|
|
|
+ const { category, label, amount, date, year, notes, recoverable } = req.body;
|
|
|
|
|
|
|
|
let invoice_path = charge.invoice_path;
|
|
let invoice_path = charge.invoice_path;
|
|
|
let invoice_name = charge.invoice_name;
|
|
let invoice_name = charge.invoice_name;
|
|
@@ -88,9 +88,9 @@ router.put('/:id', upload.single('invoice'), (req, res) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
db.prepare(`
|
|
db.prepare(`
|
|
|
- UPDATE charges SET category=?, label=?, amount=?, date=?, year=?, invoice_path=?, invoice_name=?, notes=?
|
|
|
|
|
|
|
+ UPDATE charges SET category=?, label=?, amount=?, date=?, year=?, invoice_path=?, invoice_name=?, notes=?, recoverable=?
|
|
|
WHERE id=?
|
|
WHERE id=?
|
|
|
- `).run(category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null, req.params.id);
|
|
|
|
|
|
|
+ `).run(category, label, parseFloat(amount), date, parseInt(year), invoice_path, invoice_name, notes || null, recoverable === 'false' || recoverable === '0' ? 0 : 1, req.params.id);
|
|
|
|
|
|
|
|
res.json({ success: true });
|
|
res.json({ success: true });
|
|
|
});
|
|
});
|
|
@@ -129,18 +129,32 @@ router.get('/bilan/:property_id/:year', (req, res) => {
|
|
|
const prop = db.prepare('SELECT * FROM properties WHERE id = ? AND user_id = ?').get(property_id, req.userId);
|
|
const prop = db.prepare('SELECT * FROM properties WHERE id = ? AND user_id = ?').get(property_id, req.userId);
|
|
|
if (!prop) return res.status(403).json({ error: 'Bien non autorisé' });
|
|
if (!prop) return res.status(403).json({ error: 'Bien non autorisé' });
|
|
|
|
|
|
|
|
- // Total real charges for this property/year
|
|
|
|
|
|
|
+ // Total recoverable charges for this property/year (for tenant billing)
|
|
|
const totalChargesRow = db.prepare(`
|
|
const totalChargesRow = db.prepare(`
|
|
|
SELECT SUM(amount) as total, COUNT(*) as count
|
|
SELECT SUM(amount) as total, COUNT(*) as count
|
|
|
- FROM charges WHERE property_id = ? AND year = ? AND user_id = ?
|
|
|
|
|
|
|
+ FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
|
|
|
+ `).get(property_id, year, req.userId);
|
|
|
|
|
+
|
|
|
|
|
+ // Total non-recoverable (landlord's own charges, for info)
|
|
|
|
|
+ const totalNonRecovRow = db.prepare(`
|
|
|
|
|
+ SELECT SUM(amount) as total, COUNT(*) as count
|
|
|
|
|
+ FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 0
|
|
|
`).get(property_id, year, req.userId);
|
|
`).get(property_id, year, req.userId);
|
|
|
|
|
|
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
|
|
|
+ const totalNonRecoverable = totalNonRecovRow?.total || 0;
|
|
|
|
|
|
|
|
- // Charges by category
|
|
|
|
|
|
|
+ // Charges by category (recoverable only)
|
|
|
const chargesByCategory = db.prepare(`
|
|
const chargesByCategory = db.prepare(`
|
|
|
SELECT category, SUM(amount) as total
|
|
SELECT category, SUM(amount) as total
|
|
|
- FROM charges WHERE property_id = ? AND year = ? AND user_id = ?
|
|
|
|
|
|
|
+ FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
|
|
|
+ GROUP BY category ORDER BY total DESC
|
|
|
|
|
+ `).all(property_id, year, req.userId);
|
|
|
|
|
+
|
|
|
|
|
+ // Non-recoverable by category (for info)
|
|
|
|
|
+ const chargesNonRecovByCategory = db.prepare(`
|
|
|
|
|
+ SELECT category, SUM(amount) as total
|
|
|
|
|
+ FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 0
|
|
|
GROUP BY category ORDER BY total DESC
|
|
GROUP BY category ORDER BY total DESC
|
|
|
`).all(property_id, year, req.userId);
|
|
`).all(property_id, year, req.userId);
|
|
|
|
|
|
|
@@ -190,8 +204,10 @@ router.get('/bilan/:property_id/:year', (req, res) => {
|
|
|
property: prop,
|
|
property: prop,
|
|
|
year: parseInt(year),
|
|
year: parseInt(year),
|
|
|
total_charges_reelles: Math.round(totalChargesReelles * 100) / 100,
|
|
total_charges_reelles: Math.round(totalChargesReelles * 100) / 100,
|
|
|
|
|
+ total_non_recoverable: Math.round(totalNonRecoverable * 100) / 100,
|
|
|
charges_count: totalChargesRow?.count || 0,
|
|
charges_count: totalChargesRow?.count || 0,
|
|
|
charges_by_category: chargesByCategory,
|
|
charges_by_category: chargesByCategory,
|
|
|
|
|
+ charges_non_recov_by_category: chargesNonRecovByCategory,
|
|
|
total_provisions_percues: Math.round(totalProvisions * 100) / 100,
|
|
total_provisions_percues: Math.round(totalProvisions * 100) / 100,
|
|
|
bilans
|
|
bilans
|
|
|
});
|
|
});
|
|
@@ -209,7 +225,7 @@ router.get('/bilan/:property_id/:year/pdf', async (req, res) => {
|
|
|
|
|
|
|
|
const totalChargesRow = db.prepare(`
|
|
const totalChargesRow = db.prepare(`
|
|
|
SELECT SUM(amount) as total, COUNT(*) as count FROM charges
|
|
SELECT SUM(amount) as total, COUNT(*) as count FROM charges
|
|
|
- WHERE property_id = ? AND year = ? AND user_id = ?
|
|
|
|
|
|
|
+ WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
|
`).get(property_id, year, req.userId);
|
|
`).get(property_id, year, req.userId);
|
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
|
|
|
|
|
@@ -220,7 +236,7 @@ router.get('/bilan/:property_id/:year/pdf', async (req, res) => {
|
|
|
|
|
|
|
|
const chargesByCategory = db.prepare(`
|
|
const chargesByCategory = db.prepare(`
|
|
|
SELECT category, SUM(amount) as total FROM charges
|
|
SELECT category, SUM(amount) as total FROM charges
|
|
|
- WHERE property_id = ? AND year = ? AND user_id = ?
|
|
|
|
|
|
|
+ WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
|
GROUP BY category ORDER BY total DESC
|
|
GROUP BY category ORDER BY total DESC
|
|
|
`).all(property_id, year, req.userId);
|
|
`).all(property_id, year, req.userId);
|
|
|
|
|
|