|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
const multer = require('multer');
|
|
const multer = require('multer');
|
|
|
const PDFDocument = require('pdfkit');
|
|
const PDFDocument = require('pdfkit');
|
|
|
const { PDFDocument: LibPDFDocument, rgb, StandardFonts } = require('pdf-lib');
|
|
const { PDFDocument: LibPDFDocument, rgb, StandardFonts } = require('pdf-lib');
|
|
|
-const db = require('../db');
|
|
|
|
|
|
|
+const { get, all, run, transaction } = require('../db');
|
|
|
const { authMiddleware } = require('../auth');
|
|
const { authMiddleware } = require('../auth');
|
|
|
|
|
|
|
|
const router = express.Router();
|
|
const router = express.Router();
|
|
@@ -32,7 +32,7 @@ const upload = multer({
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// List charges (filter by property_id, year)
|
|
// List charges (filter by property_id, year)
|
|
|
-router.get('/', (req, res) => {
|
|
|
|
|
|
|
+router.get('/', async (req, res) => {
|
|
|
const { property_id, year } = req.query;
|
|
const { property_id, year } = req.query;
|
|
|
let query = `
|
|
let query = `
|
|
|
SELECT c.*, p.name as property_name
|
|
SELECT c.*, p.name as property_name
|
|
@@ -44,32 +44,32 @@ router.get('/', (req, res) => {
|
|
|
if (property_id) { query += ' AND c.property_id = ?'; params.push(property_id); }
|
|
if (property_id) { query += ' AND c.property_id = ?'; params.push(property_id); }
|
|
|
if (year) { query += ' AND c.year = ?'; params.push(year); }
|
|
if (year) { query += ' AND c.year = ?'; params.push(year); }
|
|
|
query += ' ORDER BY c.date DESC';
|
|
query += ' ORDER BY c.date DESC';
|
|
|
- res.json(db.prepare(query).all(...params));
|
|
|
|
|
|
|
+ res.json(await all(query, params));
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Create charge (with optional invoice file)
|
|
// Create charge (with optional invoice file)
|
|
|
-router.post('/', upload.single('invoice'), (req, res) => {
|
|
|
|
|
|
|
+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 } = 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' });
|
|
|
|
|
|
|
|
- const prop = db.prepare('SELECT id FROM properties WHERE id = ? AND user_id = ?').get(property_id, req.userId);
|
|
|
|
|
|
|
+ const prop = await get('SELECT id FROM properties WHERE id = ? AND user_id = ?', [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é' });
|
|
|
|
|
|
|
|
const invoice_path = req.file ? req.file.filename : null;
|
|
const invoice_path = req.file ? req.file.filename : null;
|
|
|
const invoice_name = req.file ? req.file.originalname : null;
|
|
const invoice_name = req.file ? req.file.originalname : null;
|
|
|
|
|
|
|
|
- const result = db.prepare(`
|
|
|
|
|
|
|
+ const result = await run(`
|
|
|
INSERT INTO charges (user_id, property_id, category, label, amount, date, year, invoice_path, invoice_name, notes, recoverable)
|
|
INSERT INTO charges (user_id, property_id, category, label, amount, date, year, invoice_path, invoice_name, notes, recoverable)
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
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);
|
|
|
|
|
|
|
+ `, [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.insertId });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Update charge (with optional new invoice)
|
|
// Update charge (with optional new invoice)
|
|
|
-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);
|
|
|
|
|
|
|
+router.put('/:id', upload.single('invoice'), async (req, res) => {
|
|
|
|
|
+ const charge = await get('SELECT * FROM charges WHERE id = ? AND user_id = ?', [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, recoverable } = req.body;
|
|
const { category, label, amount, date, year, notes, recoverable } = req.body;
|
|
@@ -87,17 +87,17 @@ router.put('/:id', upload.single('invoice'), (req, res) => {
|
|
|
invoice_name = req.file.originalname;
|
|
invoice_name = req.file.originalname;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- db.prepare(`
|
|
|
|
|
|
|
+ await run(`
|
|
|
UPDATE charges SET category=?, label=?, amount=?, date=?, year=?, invoice_path=?, invoice_name=?, notes=?, recoverable=?
|
|
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, recoverable === 'false' || recoverable === '0' ? 0 : 1, req.params.id);
|
|
|
|
|
|
|
+ `, [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 });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Delete charge
|
|
// Delete charge
|
|
|
-router.delete('/:id', (req, res) => {
|
|
|
|
|
- const charge = db.prepare('SELECT * FROM charges WHERE id = ? AND user_id = ?').get(req.params.id, req.userId);
|
|
|
|
|
|
|
+router.delete('/:id', async (req, res) => {
|
|
|
|
|
+ const charge = await get('SELECT * FROM charges WHERE id = ? AND user_id = ?', [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' });
|
|
|
|
|
|
|
|
if (charge.invoice_path) {
|
|
if (charge.invoice_path) {
|
|
@@ -105,13 +105,13 @@ router.delete('/:id', (req, res) => {
|
|
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- db.prepare('DELETE FROM charges WHERE id = ?').run(req.params.id);
|
|
|
|
|
|
|
+ await run('DELETE FROM charges WHERE id = ?', [req.params.id]);
|
|
|
res.json({ success: true });
|
|
res.json({ success: true });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Download invoice
|
|
// Download invoice
|
|
|
-router.get('/:id/invoice', (req, res) => {
|
|
|
|
|
- const charge = db.prepare('SELECT * FROM charges WHERE id = ? AND user_id = ?').get(req.params.id, req.userId);
|
|
|
|
|
|
|
+router.get('/:id/invoice', async (req, res) => {
|
|
|
|
|
+ const charge = await get('SELECT * FROM charges WHERE id = ? AND user_id = ?', [req.params.id, req.userId]);
|
|
|
if (!charge || !charge.invoice_path) return res.status(404).json({ error: 'Facture non trouvée' });
|
|
if (!charge || !charge.invoice_path) return res.status(404).json({ error: 'Facture non trouvée' });
|
|
|
|
|
|
|
|
const filePath = path.join(uploadDir, charge.invoice_path);
|
|
const filePath = path.join(uploadDir, charge.invoice_path);
|
|
@@ -122,67 +122,68 @@ router.get('/:id/invoice', (req, res) => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Annual bilan for a property
|
|
// Annual bilan for a property
|
|
|
-router.get('/bilan/:property_id/:year', (req, res) => {
|
|
|
|
|
|
|
+router.get('/bilan/:property_id/:year', async (req, res) => {
|
|
|
const { property_id, year } = req.params;
|
|
const { property_id, year } = req.params;
|
|
|
|
|
|
|
|
// Verify property ownership
|
|
// Verify property ownership
|
|
|
- const prop = db.prepare('SELECT * FROM properties WHERE id = ? AND user_id = ?').get(property_id, req.userId);
|
|
|
|
|
|
|
+ const prop = await get('SELECT * FROM properties WHERE id = ? AND user_id = ?', [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 recoverable charges for this property/year (for tenant billing)
|
|
// Total recoverable charges for this property/year (for tenant billing)
|
|
|
- const totalChargesRow = db.prepare(`
|
|
|
|
|
|
|
+ const totalChargesRow = await get(`
|
|
|
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 = ? AND recoverable = 1
|
|
FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
|
- `).get(property_id, year, req.userId);
|
|
|
|
|
|
|
+ `, [property_id, year, req.userId]);
|
|
|
|
|
|
|
|
// Total non-recoverable (landlord's own charges, for info)
|
|
// Total non-recoverable (landlord's own charges, for info)
|
|
|
- const totalNonRecovRow = db.prepare(`
|
|
|
|
|
|
|
+ const totalNonRecovRow = await get(`
|
|
|
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 = ? AND recoverable = 0
|
|
FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 0
|
|
|
- `).get(property_id, year, req.userId);
|
|
|
|
|
|
|
+ `, [property_id, year, req.userId]);
|
|
|
|
|
|
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
|
const totalNonRecoverable = totalNonRecovRow?.total || 0;
|
|
const totalNonRecoverable = totalNonRecovRow?.total || 0;
|
|
|
|
|
|
|
|
// Charges by category (recoverable only)
|
|
// Charges by category (recoverable only)
|
|
|
- const chargesByCategory = db.prepare(`
|
|
|
|
|
|
|
+ const chargesByCategory = await all(`
|
|
|
SELECT category, SUM(amount) as total
|
|
SELECT category, SUM(amount) as total
|
|
|
FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
FROM charges 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);
|
|
|
|
|
|
|
+ `, [property_id, year, req.userId]);
|
|
|
|
|
|
|
|
// Non-recoverable by category (for info)
|
|
// Non-recoverable by category (for info)
|
|
|
- const chargesNonRecovByCategory = db.prepare(`
|
|
|
|
|
|
|
+ const chargesNonRecovByCategory = await all(`
|
|
|
SELECT category, SUM(amount) as total
|
|
SELECT category, SUM(amount) as total
|
|
|
FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 0
|
|
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);
|
|
|
|
|
|
|
+ `, [property_id, year, req.userId]);
|
|
|
|
|
|
|
|
// All leases on this property (active or that were active during the year)
|
|
// All leases on this property (active or that were active during the year)
|
|
|
- const leases = db.prepare(`
|
|
|
|
|
|
|
+ const leases = await all(`
|
|
|
SELECT l.*, t.first_name, t.last_name, t.email
|
|
SELECT l.*, t.first_name, t.last_name, t.email
|
|
|
FROM leases l
|
|
FROM leases l
|
|
|
JOIN tenants t ON l.tenant_id = t.id
|
|
JOIN tenants t ON l.tenant_id = t.id
|
|
|
WHERE l.property_id = ? AND l.user_id = ?
|
|
WHERE l.property_id = ? AND l.user_id = ?
|
|
|
AND (l.start_date <= ? AND (l.end_date IS NULL OR l.end_date >= ?))
|
|
AND (l.start_date <= ? AND (l.end_date IS NULL OR l.end_date >= ?))
|
|
|
- `).all(property_id, req.userId, `${year}-12-31`, `${year}-01-01`);
|
|
|
|
|
|
|
+ `, [property_id, req.userId, `${year}-12-31`, `${year}-01-01`]);
|
|
|
|
|
|
|
|
// For each lease, get provisions perçues for the year
|
|
// For each lease, get provisions perçues for the year
|
|
|
- const leaseBilans = leases.map(lease => {
|
|
|
|
|
- const provisions = db.prepare(`
|
|
|
|
|
|
|
+ const leaseBilans = [];
|
|
|
|
|
+ for (const lease of leases) {
|
|
|
|
|
+ const provisions = await get(`
|
|
|
SELECT SUM(charges_paid) as total
|
|
SELECT SUM(charges_paid) as total
|
|
|
FROM payments
|
|
FROM payments
|
|
|
WHERE lease_id = ? AND period_year = ?
|
|
WHERE lease_id = ? AND period_year = ?
|
|
|
- `).get(lease.id, year);
|
|
|
|
|
|
|
+ `, [lease.id, year]);
|
|
|
|
|
|
|
|
- return {
|
|
|
|
|
|
|
+ leaseBilans.push({
|
|
|
lease_id: lease.id,
|
|
lease_id: lease.id,
|
|
|
tenant_name: `${lease.first_name} ${lease.last_name}`,
|
|
tenant_name: `${lease.first_name} ${lease.last_name}`,
|
|
|
tenant_email: lease.email,
|
|
tenant_email: lease.email,
|
|
|
monthly_provision: lease.charges_amount,
|
|
monthly_provision: lease.charges_amount,
|
|
|
provisions_percues: provisions?.total || 0
|
|
provisions_percues: provisions?.total || 0
|
|
|
- };
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Calculate total provisions across all tenants for proportional split
|
|
// Calculate total provisions across all tenants for proportional split
|
|
|
const totalProvisions = leaseBilans.reduce((s, l) => s + l.provisions_percues, 0);
|
|
const totalProvisions = leaseBilans.reduce((s, l) => s + l.provisions_percues, 0);
|
|
@@ -214,48 +215,48 @@ router.get('/bilan/:property_id/:year', (req, res) => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Close annual bilan: create charge_regularization records for each tenant
|
|
// Close annual bilan: create charge_regularization records for each tenant
|
|
|
-router.post('/bilan/:property_id/:year/close', (req, res) => {
|
|
|
|
|
|
|
+router.post('/bilan/:property_id/:year/close', async (req, res) => {
|
|
|
const { property_id, year } = req.params;
|
|
const { property_id, year } = req.params;
|
|
|
|
|
|
|
|
- const prop = db.prepare('SELECT * FROM properties WHERE id = ? AND user_id = ?').get(property_id, req.userId);
|
|
|
|
|
|
|
+ const prop = await get('SELECT * FROM properties WHERE id = ? AND user_id = ?', [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é' });
|
|
|
|
|
|
|
|
// Check not already closed
|
|
// Check not already closed
|
|
|
- const alreadyClosed = db.prepare(
|
|
|
|
|
- 'SELECT id FROM charge_regularizations WHERE user_id = ? AND year = ? AND lease_id IN (SELECT id FROM leases WHERE property_id = ?)'
|
|
|
|
|
- ).get(req.userId, year, property_id);
|
|
|
|
|
|
|
+ const alreadyClosed = await get(
|
|
|
|
|
+ 'SELECT id FROM charge_regularizations WHERE user_id = ? AND year = ? AND lease_id IN (SELECT id FROM leases WHERE property_id = ?)',
|
|
|
|
|
+ [req.userId, year, property_id]
|
|
|
|
|
+ );
|
|
|
if (alreadyClosed) return res.status(409).json({ error: `Le bilan ${year} a déjà été clôturé pour ce bien` });
|
|
if (alreadyClosed) return res.status(409).json({ error: `Le bilan ${year} a déjà été clôturé pour ce bien` });
|
|
|
|
|
|
|
|
// Re-compute bilan
|
|
// Re-compute bilan
|
|
|
- const totalChargesRow = db.prepare(
|
|
|
|
|
- 'SELECT SUM(amount) as total FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1'
|
|
|
|
|
- ).get(property_id, year, req.userId);
|
|
|
|
|
|
|
+ const totalChargesRow = await get(
|
|
|
|
|
+ 'SELECT SUM(amount) as total FROM charges WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1',
|
|
|
|
|
+ [property_id, year, req.userId]
|
|
|
|
|
+ );
|
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
|
|
|
|
|
|
- const leases = db.prepare(`
|
|
|
|
|
|
|
+ const leases = await all(`
|
|
|
SELECT l.*, t.first_name, t.last_name FROM leases l
|
|
SELECT l.*, t.first_name, t.last_name FROM leases l
|
|
|
JOIN tenants t ON l.tenant_id = t.id
|
|
JOIN tenants t ON l.tenant_id = t.id
|
|
|
WHERE l.property_id = ? AND l.user_id = ?
|
|
WHERE l.property_id = ? AND l.user_id = ?
|
|
|
AND (l.start_date <= ? AND (l.end_date IS NULL OR l.end_date >= ?))
|
|
AND (l.start_date <= ? AND (l.end_date IS NULL OR l.end_date >= ?))
|
|
|
- `).all(property_id, req.userId, `${year}-12-31`, `${year}-01-01`);
|
|
|
|
|
|
|
+ `, [property_id, req.userId, `${year}-12-31`, `${year}-01-01`]);
|
|
|
|
|
|
|
|
if (leases.length === 0) return res.status(400).json({ error: 'Aucun locataire actif sur cette période' });
|
|
if (leases.length === 0) return res.status(400).json({ error: 'Aucun locataire actif sur cette période' });
|
|
|
|
|
|
|
|
- const leaseBilans = leases.map(lease => {
|
|
|
|
|
- const provisions = db.prepare(
|
|
|
|
|
- 'SELECT SUM(charges_paid) as total FROM payments WHERE lease_id = ? AND period_year = ?'
|
|
|
|
|
- ).get(lease.id, year);
|
|
|
|
|
- return { lease_id: lease.id, tenant: `${lease.first_name} ${lease.last_name}`, provisions_percues: provisions?.total || 0 };
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const leaseBilans = [];
|
|
|
|
|
+ for (const lease of leases) {
|
|
|
|
|
+ const provisions = await get(
|
|
|
|
|
+ 'SELECT SUM(charges_paid) as total FROM payments WHERE lease_id = ? AND period_year = ?',
|
|
|
|
|
+ [lease.id, year]
|
|
|
|
|
+ );
|
|
|
|
|
+ leaseBilans.push({ lease_id: lease.id, tenant: `${lease.first_name} ${lease.last_name}`, provisions_percues: provisions?.total || 0 });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
const totalProvisions = leaseBilans.reduce((s, l) => s + l.provisions_percues, 0);
|
|
const totalProvisions = leaseBilans.reduce((s, l) => s + l.provisions_percues, 0);
|
|
|
|
|
|
|
|
- const insertReg = db.prepare(
|
|
|
|
|
- 'INSERT INTO charge_regularizations (lease_id, user_id, year, amount, notes) VALUES (?, ?, ?, ?, ?)'
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const closeAll = db.transaction(() => {
|
|
|
|
|
- const results = [];
|
|
|
|
|
|
|
+ const results = await transaction(async (conn) => {
|
|
|
|
|
+ const out = [];
|
|
|
for (const l of leaseBilans) {
|
|
for (const l of leaseBilans) {
|
|
|
const ratio = totalProvisions > 0 ? l.provisions_percues / totalProvisions : 1 / leaseBilans.length;
|
|
const ratio = totalProvisions > 0 ? l.provisions_percues / totalProvisions : 1 / leaseBilans.length;
|
|
|
const quote_part = totalChargesReelles * ratio;
|
|
const quote_part = totalChargesReelles * ratio;
|
|
@@ -263,14 +264,16 @@ router.post('/bilan/:property_id/:year/close', (req, res) => {
|
|
|
// trop_percu < 0 = locataire doit payer en plus → débit
|
|
// trop_percu < 0 = locataire doit payer en plus → débit
|
|
|
const trop_percu = Math.round((l.provisions_percues - quote_part) * 100) / 100;
|
|
const trop_percu = Math.round((l.provisions_percues - quote_part) * 100) / 100;
|
|
|
// Stored as: positive = credit tenant (réduction loyer), negative = debit tenant (supplément)
|
|
// Stored as: positive = credit tenant (réduction loyer), negative = debit tenant (supplément)
|
|
|
- insertReg.run(l.lease_id, req.userId, parseInt(year), trop_percu,
|
|
|
|
|
- `Régularisation charges ${year} — quote-part ${Math.round(ratio * 10000) / 100}%`);
|
|
|
|
|
- results.push({ lease_id: l.lease_id, tenant: l.tenant, trop_percu });
|
|
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ 'INSERT INTO charge_regularizations (lease_id, user_id, year, amount, notes) VALUES (?, ?, ?, ?, ?)',
|
|
|
|
|
+ [l.lease_id, req.userId, parseInt(year), trop_percu,
|
|
|
|
|
+ `Régularisation charges ${year} — quote-part ${Math.round(ratio * 10000) / 100}%`]
|
|
|
|
|
+ );
|
|
|
|
|
+ out.push({ lease_id: l.lease_id, tenant: l.tenant, trop_percu });
|
|
|
}
|
|
}
|
|
|
- return results;
|
|
|
|
|
|
|
+ return out;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const results = closeAll();
|
|
|
|
|
res.json({ success: true, year: parseInt(year), regularizations: results });
|
|
res.json({ success: true, year: parseInt(year), regularizations: results });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -278,45 +281,46 @@ router.post('/bilan/:property_id/:year/close', (req, res) => {
|
|
|
router.get('/bilan/:property_id/:year/pdf', async (req, res) => {
|
|
router.get('/bilan/:property_id/:year/pdf', async (req, res) => {
|
|
|
const { property_id, year } = req.params;
|
|
const { property_id, year } = req.params;
|
|
|
|
|
|
|
|
- const prop = db.prepare('SELECT * FROM properties WHERE id = ? AND user_id = ?').get(property_id, req.userId);
|
|
|
|
|
|
|
+ const prop = await get('SELECT * FROM properties WHERE id = ? AND user_id = ?', [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é' });
|
|
|
|
|
|
|
|
- const owner = db.prepare('SELECT name, first_name, last_name, email, address, phone FROM users WHERE id = ?').get(req.userId);
|
|
|
|
|
|
|
+ const owner = await get('SELECT name, first_name, last_name, email, address, phone FROM users WHERE id = ?', [req.userId]);
|
|
|
const ownerFullName = (owner.first_name && owner.last_name) ? `${owner.first_name} ${owner.last_name}` : owner.name;
|
|
const ownerFullName = (owner.first_name && owner.last_name) ? `${owner.first_name} ${owner.last_name}` : owner.name;
|
|
|
|
|
|
|
|
- const totalChargesRow = db.prepare(`
|
|
|
|
|
|
|
+ const totalChargesRow = await get(`
|
|
|
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 = ? AND recoverable = 1
|
|
WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
|
- `).get(property_id, year, req.userId);
|
|
|
|
|
|
|
+ `, [property_id, year, req.userId]);
|
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
const totalChargesReelles = totalChargesRow?.total || 0;
|
|
|
|
|
|
|
|
- const chargesList = db.prepare(`
|
|
|
|
|
|
|
+ const chargesList = await all(`
|
|
|
SELECT * FROM charges WHERE property_id = ? AND year = ? AND user_id = ?
|
|
SELECT * FROM charges WHERE property_id = ? AND year = ? AND user_id = ?
|
|
|
ORDER BY date ASC
|
|
ORDER BY date ASC
|
|
|
- `).all(property_id, year, req.userId);
|
|
|
|
|
|
|
+ `, [property_id, year, req.userId]);
|
|
|
|
|
|
|
|
- const chargesByCategory = db.prepare(`
|
|
|
|
|
|
|
+ const chargesByCategory = await all(`
|
|
|
SELECT category, SUM(amount) as total FROM charges
|
|
SELECT category, SUM(amount) as total FROM charges
|
|
|
WHERE property_id = ? AND year = ? AND user_id = ? AND recoverable = 1
|
|
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);
|
|
|
|
|
|
|
+ `, [property_id, year, req.userId]);
|
|
|
|
|
|
|
|
- const leases = db.prepare(`
|
|
|
|
|
|
|
+ const leases = await all(`
|
|
|
SELECT l.*, t.first_name, t.last_name, t.email FROM leases l
|
|
SELECT l.*, t.first_name, t.last_name, t.email FROM leases l
|
|
|
JOIN tenants t ON l.tenant_id = t.id
|
|
JOIN tenants t ON l.tenant_id = t.id
|
|
|
WHERE l.property_id = ? AND l.user_id = ?
|
|
WHERE l.property_id = ? AND l.user_id = ?
|
|
|
AND (l.start_date <= ? AND (l.end_date IS NULL OR l.end_date >= ?))
|
|
AND (l.start_date <= ? AND (l.end_date IS NULL OR l.end_date >= ?))
|
|
|
- `).all(property_id, req.userId, `${year}-12-31`, `${year}-01-01`);
|
|
|
|
|
|
|
+ `, [property_id, req.userId, `${year}-12-31`, `${year}-01-01`]);
|
|
|
|
|
|
|
|
- const leaseBilans = leases.map(lease => {
|
|
|
|
|
- const provisions = db.prepare(`SELECT SUM(charges_paid) as total FROM payments WHERE lease_id = ? AND period_year = ?`).get(lease.id, year);
|
|
|
|
|
- return {
|
|
|
|
|
|
|
+ const leaseBilans = [];
|
|
|
|
|
+ for (const lease of leases) {
|
|
|
|
|
+ const provisions = await get(`SELECT SUM(charges_paid) as total FROM payments WHERE lease_id = ? AND period_year = ?`, [lease.id, year]);
|
|
|
|
|
+ leaseBilans.push({
|
|
|
lease_id: lease.id,
|
|
lease_id: lease.id,
|
|
|
tenant_name: `${lease.first_name} ${lease.last_name}`,
|
|
tenant_name: `${lease.first_name} ${lease.last_name}`,
|
|
|
tenant_email: lease.email,
|
|
tenant_email: lease.email,
|
|
|
provisions_percues: provisions?.total || 0
|
|
provisions_percues: provisions?.total || 0
|
|
|
- };
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
const totalProvisions = leaseBilans.reduce((s, l) => s + l.provisions_percues, 0);
|
|
const totalProvisions = leaseBilans.reduce((s, l) => s + l.provisions_percues, 0);
|
|
|
const bilans = leaseBilans.map(l => {
|
|
const bilans = leaseBilans.map(l => {
|
|
|
const ratio = totalProvisions > 0 ? l.provisions_percues / totalProvisions : (leaseBilans.length > 0 ? 1 / leaseBilans.length : 0);
|
|
const ratio = totalProvisions > 0 ? l.provisions_percues / totalProvisions : (leaseBilans.length > 0 ? 1 / leaseBilans.length : 0);
|