receipts.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. const express = require('express');
  2. const PDFDocument = require('pdfkit');
  3. const pool = require('../db');
  4. const { authMiddleware } = require('../auth');
  5. const router = express.Router();
  6. router.use(authMiddleware);
  7. const MONTHS_FR = ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'];
  8. router.get('/:paymentId', async (req, res) => {
  9. const payment = (await pool.query(`
  10. SELECT p.*,
  11. l.rent_amount, l.charges_amount, l.deposit_amount,
  12. pr.name as property_name, pr.address as property_address, pr.city as property_city, pr.zip_code as property_zip,
  13. t.first_name as tenant_first_name, t.last_name as tenant_last_name, t.email as tenant_email,
  14. -- Propriétaire bailleur (owners table en priorité, fallback profil utilisateur)
  15. o.type as o_type,
  16. o.first_name as o_first_name, o.last_name as o_last_name,
  17. o.company_name as o_company_name, o.siret as o_siret,
  18. o.address as o_address, o.zip_code as o_zip, o.city as o_city,
  19. o.email as o_email, o.phone as o_phone,
  20. -- Fallback gestionnaire
  21. u.name as u_name, u.email as u_email,
  22. u.first_name as u_first_name, u.last_name as u_last_name,
  23. u.address as u_address, u.phone as u_phone
  24. FROM payments p
  25. JOIN leases l ON p.lease_id = l.id
  26. JOIN properties pr ON l.property_id = pr.id
  27. JOIN tenants t ON l.tenant_id = t.id
  28. JOIN users u ON p.user_id = u.id
  29. LEFT JOIN owners o ON l.owner_id = o.id
  30. WHERE p.id = $1 AND p.user_id = $2
  31. `, [req.params.paymentId, req.userId])).rows[0];
  32. if (!payment) return res.status(404).json({ error: 'Paiement non trouvé' });
  33. // Résoudre le nom du bailleur : owner en priorité, sinon profil gestionnaire
  34. let ownerFullName, ownerAddress, ownerEmail, ownerPhone, ownerSiret;
  35. if (payment.o_type) {
  36. ownerFullName = payment.o_type === 'morale'
  37. ? payment.o_company_name
  38. : `${payment.o_first_name || ''} ${payment.o_last_name || ''}`.trim();
  39. ownerSiret = payment.o_siret;
  40. ownerAddress = [payment.o_address, payment.o_zip && payment.o_city ? `${payment.o_zip} ${payment.o_city}` : (payment.o_zip || payment.o_city)].filter(Boolean).join(', ');
  41. ownerEmail = payment.o_email;
  42. ownerPhone = payment.o_phone;
  43. } else {
  44. ownerFullName = (payment.u_first_name && payment.u_last_name)
  45. ? `${payment.u_first_name} ${payment.u_last_name}`
  46. : payment.u_name;
  47. ownerSiret = null;
  48. ownerAddress = payment.u_address;
  49. ownerEmail = payment.u_email;
  50. ownerPhone = payment.u_phone;
  51. }
  52. const doc = new PDFDocument({ size: 'A4', margin: 50 });
  53. res.setHeader('Content-Type', 'application/pdf');
  54. res.setHeader('Content-Disposition', `attachment; filename=quittance_${payment.period_year}_${String(payment.period_month).padStart(2,'0')}.pdf`);
  55. doc.pipe(res);
  56. const monthName = MONTHS_FR[payment.period_month - 1];
  57. const total = payment.rent_paid + payment.charges_paid;
  58. // Header
  59. doc.fontSize(22).font('Helvetica-Bold').text('QUITTANCE DE LOYER', { align: 'center' });
  60. doc.moveDown(0.3);
  61. doc.fontSize(14).font('Helvetica').text(`Période : ${monthName} ${payment.period_year}`, { align: 'center' });
  62. doc.moveDown(1.5);
  63. // Separator
  64. doc.moveTo(50, doc.y).lineTo(545, doc.y).strokeColor('#2563eb').lineWidth(2).stroke();
  65. doc.moveDown(1);
  66. // Bailleur
  67. doc.fontSize(12).font('Helvetica-Bold').text('BAILLEUR');
  68. doc.font('Helvetica').fontSize(11).text(ownerFullName);
  69. if (ownerSiret) doc.text(`SIRET : ${ownerSiret}`);
  70. if (ownerAddress) doc.text(ownerAddress);
  71. if (ownerEmail) doc.text(ownerEmail);
  72. if (ownerPhone) doc.text(ownerPhone);
  73. doc.moveDown(1);
  74. // Locataire
  75. doc.fontSize(12).font('Helvetica-Bold').text('LOCATAIRE');
  76. doc.font('Helvetica').fontSize(11)
  77. .text(`${payment.tenant_first_name} ${payment.tenant_last_name}`)
  78. .text(payment.tenant_email || '');
  79. doc.moveDown(1);
  80. // Bien loué
  81. doc.fontSize(12).font('Helvetica-Bold').text('BIEN LOUÉ');
  82. doc.font('Helvetica').fontSize(11)
  83. .text(payment.property_name)
  84. .text(`${payment.property_address}`)
  85. .text(`${payment.property_zip} ${payment.property_city}`);
  86. doc.moveDown(1);
  87. // Separator
  88. doc.moveTo(50, doc.y).lineTo(545, doc.y).strokeColor('#e5e7eb').lineWidth(1).stroke();
  89. doc.moveDown(1);
  90. // Détail paiement
  91. doc.fontSize(12).font('Helvetica-Bold').text('DÉTAIL DU RÈGLEMENT');
  92. doc.moveDown(0.5);
  93. const col1 = 50, col2 = 400;
  94. const drawRow = (label, value, bold = false, color = '#000000') => {
  95. const y = doc.y;
  96. doc.font(bold ? 'Helvetica-Bold' : 'Helvetica').fontSize(11).fillColor(color);
  97. doc.text(label, col1, y);
  98. doc.text(`${Number(value).toFixed(2)} €`, col2, y, { width: 100, align: 'right' });
  99. doc.fillColor('#000000');
  100. doc.moveDown(0.6);
  101. };
  102. const isProrata = payment.is_prorata && payment.prorata_days && payment.prorata_total_days;
  103. if (isProrata) {
  104. drawRow(`Loyer au prorata (${payment.prorata_days}/${payment.prorata_total_days} jours)`, payment.rent_paid);
  105. } else {
  106. drawRow('Loyer hors charges', payment.rent_paid);
  107. }
  108. drawRow('Charges locatives', payment.charges_paid);
  109. const reg = payment.charge_regularization || 0;
  110. if (reg !== 0) {
  111. // positive reg = trop-perçu → déduit du loyer (bonne nouvelle pour locataire)
  112. // negative reg = complément → ajouté au loyer
  113. const regLabel = reg > 0
  114. ? `Régularisation de charges — trop-perçu (déduit)`
  115. : `Régularisation de charges — complément appelé`;
  116. drawRow(regLabel, -reg, false, reg > 0 ? '#15803d' : '#c2410c');
  117. }
  118. doc.moveTo(50, doc.y).lineTo(545, doc.y).strokeColor('#e5e7eb').lineWidth(0.5).stroke();
  119. doc.moveDown(0.3);
  120. const netTotal = payment.rent_paid + payment.charges_paid - reg;
  121. drawRow('TOTAL NET À PAYER', netTotal, true);
  122. doc.moveDown(0.5);
  123. // Mode et date de paiement
  124. const paymentMethods = { virement: 'Virement bancaire', cheque: 'Chèque', especes: 'Espèces', prelevement: 'Prélèvement automatique' };
  125. doc.font('Helvetica').fontSize(11)
  126. .text(`Mode de paiement : ${paymentMethods[payment.payment_method] || payment.payment_method}`)
  127. .text(`Date de paiement : ${new Date(payment.payment_date).toLocaleDateString('fr-FR')}`);
  128. if (payment.notes) {
  129. doc.moveDown(0.5).text(`Notes : ${payment.notes}`);
  130. }
  131. doc.moveDown(2);
  132. // Attestation
  133. doc.moveTo(50, doc.y).lineTo(545, doc.y).strokeColor('#e5e7eb').lineWidth(1).stroke();
  134. doc.moveDown(1);
  135. doc.font('Helvetica').fontSize(10).fillColor('#6b7280')
  136. .text(
  137. `Je soussigné(e) ${ownerFullName}, bailleur, donne quittance à ${payment.tenant_first_name} ${payment.tenant_last_name} ` +
  138. `pour la somme de ${netTotal.toFixed(2)} € correspondant au paiement du loyer et des charges du logement situé ` +
  139. `${payment.property_address}, ${payment.property_zip} ${payment.property_city}, ` +
  140. `pour la période de ${monthName} ${payment.period_year}.`,
  141. { align: 'justify' }
  142. );
  143. doc.moveDown(3);
  144. doc.fontSize(11).fillColor('#000000').text(`Fait le ${new Date().toLocaleDateString('fr-FR')}`);
  145. doc.moveDown(2);
  146. doc.text('Signature du bailleur :', { continued: false });
  147. doc.moveDown(1);
  148. doc.moveTo(50, doc.y).lineTo(200, doc.y).strokeColor('#000').lineWidth(1).stroke();
  149. doc.end();
  150. });
  151. module.exports = router;