klydekushy commited on
Commit
98273af
·
verified ·
1 Parent(s): da721ed

Update src/DocumentGen/InvoiceRepayment.py

Browse files
Files changed (1) hide show
  1. src/DocumentGen/InvoiceRepayment.py +320 -0
src/DocumentGen/InvoiceRepayment.py CHANGED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from reportlab.lib.pagesizes import A4
2
+ from reportlab.lib import colors
3
+ from reportlab.lib.units import cm
4
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, HRFlowable
5
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
6
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
7
+ from io import BytesIO
8
+ from datetime import datetime
9
+
10
+ def generer_recu(data):
11
+ """
12
+ Génère un reçu de remboursement au format PDF
13
+
14
+ Args:
15
+ data: dict contenant toutes les informations nécessaires
16
+ {
17
+ 'numero_recu': 'REC-2026-0001',
18
+ 'trans_id': 'TRX-2026-0042',
19
+ 'date_paiement': date(2026, 1, 15),
20
+ 'client': {...},
21
+ 'loan': {...},
22
+ 'paiement': {...},
23
+ 'moyen': 'Mobile Money (Wave)',
24
+ 'reference': 'WAVE-TXN-123456'
25
+ }
26
+
27
+ Returns:
28
+ BytesIO: Buffer contenant le PDF
29
+ """
30
+ try:
31
+ # Création du buffer
32
+ buffer = BytesIO()
33
+
34
+ # Configuration du document
35
+ doc = SimpleDocTemplate(
36
+ buffer,
37
+ pagesize=A4,
38
+ rightMargin=2*cm,
39
+ leftMargin=2*cm,
40
+ topMargin=2*cm,
41
+ bottomMargin=2*cm
42
+ )
43
+
44
+ # Container pour les éléments
45
+ elements = []
46
+
47
+ # Styles
48
+ styles = getSampleStyleSheet()
49
+
50
+ # Style personnalisé pour le titre
51
+ title_style = ParagraphStyle(
52
+ 'CustomTitle',
53
+ parent=styles['Heading1'],
54
+ fontSize=20,
55
+ textColor=colors.HexColor('#58a6ff'),
56
+ spaceAfter=12,
57
+ alignment=TA_CENTER,
58
+ fontName='Helvetica-Bold'
59
+ )
60
+
61
+ # Style pour les sous-titres
62
+ subtitle_style = ParagraphStyle(
63
+ 'CustomSubtitle',
64
+ parent=styles['Heading2'],
65
+ fontSize=14,
66
+ textColor=colors.HexColor('#8b949e'),
67
+ spaceAfter=10,
68
+ spaceBefore=15,
69
+ fontName='Helvetica-Bold'
70
+ )
71
+
72
+ # Style pour le texte normal
73
+ normal_style = ParagraphStyle(
74
+ 'CustomNormal',
75
+ parent=styles['Normal'],
76
+ fontSize=10,
77
+ textColor=colors.black,
78
+ spaceAfter=6
79
+ )
80
+
81
+ # Style pour les montants importants
82
+ amount_style = ParagraphStyle(
83
+ 'AmountStyle',
84
+ parent=styles['Normal'],
85
+ fontSize=16,
86
+ textColor=colors.HexColor('#54bd4b'),
87
+ alignment=TA_CENTER,
88
+ fontName='Helvetica-Bold',
89
+ spaceAfter=10
90
+ )
91
+
92
+ # === EN-TÊTE ===
93
+ elements.append(Paragraph("REÇU DE REMBOURSEMENT", title_style))
94
+ elements.append(Spacer(1, 0.3*cm))
95
+
96
+ # Numéro de reçu et date
97
+ header_data = [
98
+ [f"N° {data['numero_recu']}", f"Date: {data['date_paiement'].strftime('%d/%m/%Y')}"]
99
+ ]
100
+ header_table = Table(header_data, colWidths=[8*cm, 8*cm])
101
+ header_table.setStyle(TableStyle([
102
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
103
+ ('FONTSIZE', (0, 0), (-1, -1), 11),
104
+ ('TEXTCOLOR', (0, 0), (0, 0), colors.HexColor('#58a6ff')),
105
+ ('TEXTCOLOR', (1, 0), (1, 0), colors.HexColor('#8b949e')),
106
+ ('ALIGN', (0, 0), (0, 0), 'LEFT'),
107
+ ('ALIGN', (1, 0), (1, 0), 'RIGHT'),
108
+ ]))
109
+ elements.append(header_table)
110
+ elements.append(Spacer(1, 0.5*cm))
111
+
112
+ # Ligne de séparation
113
+ elements.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#58a6ff')))
114
+ elements.append(Spacer(1, 0.5*cm))
115
+
116
+ # === INFORMATIONS CLIENT ===
117
+ elements.append(Paragraph("INFORMATIONS CLIENT", subtitle_style))
118
+
119
+ client = data['client']
120
+ client_data = [
121
+ ['Nom Complet:', client.get('Nom_Complet', 'N/A')],
122
+ ['N° Client:', client.get('ID_Client', 'N/A')],
123
+ ['Téléphone:', client.get('Telephone', 'N/A')],
124
+ ['Adresse:', client.get('Adresse', 'N/A')]
125
+ ]
126
+
127
+ client_table = Table(client_data, colWidths=[4*cm, 12*cm])
128
+ client_table.setStyle(TableStyle([
129
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
130
+ ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
131
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
132
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
133
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
134
+ ('LEFTPADDING', (0, 0), (-1, -1), 0),
135
+ ('RIGHTPADDING', (0, 0), (-1, -1), 0),
136
+ ]))
137
+ elements.append(client_table)
138
+ elements.append(Spacer(1, 0.5*cm))
139
+
140
+ # === INFORMATIONS PRÊT ===
141
+ elements.append(Paragraph("INFORMATIONS PRÊT", subtitle_style))
142
+
143
+ loan = data['loan']
144
+ loan_data = [
145
+ ['N° Prêt:', loan.get('ID_Pret', 'N/A')],
146
+ ['Type de Prêt:', loan.get('Type_Pret', 'N/A')],
147
+ ['Capital Initial:', f"{loan.get('Montant_Capital', 0):,.0f} XOF"],
148
+ ['Montant Total Dû:', f"{loan.get('Montant_Total', 0):,.0f} XOF"]
149
+ ]
150
+
151
+ loan_table = Table(loan_data, colWidths=[4*cm, 12*cm])
152
+ loan_table.setStyle(TableStyle([
153
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
154
+ ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
155
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
156
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
157
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
158
+ ('LEFTPADDING', (0, 0), (-1, -1), 0),
159
+ ('RIGHTPADDING', (0, 0), (-1, -1), 0),
160
+ ]))
161
+ elements.append(loan_table)
162
+ elements.append(Spacer(1, 0.5*cm))
163
+
164
+ # === DÉTAILS DU PAIEMENT ===
165
+ elements.append(Paragraph("DÉTAILS DU PAIEMENT", subtitle_style))
166
+
167
+ paiement = data['paiement']
168
+ payment_data = [
169
+ ['Échéance:', paiement.get('numero_echeance', 'N/A')],
170
+ ['Date Échéance Prévue:', paiement.get('date_echeance_prevue', 'N/A').strftime('%d/%m/%Y') if hasattr(paiement.get('date_echeance_prevue', ''), 'strftime') else paiement.get('date_echeance_prevue', 'N/A')],
171
+ ['Retard (jours):', str(paiement.get('jours_retard', 0))],
172
+ ['Statut:', paiement.get('statut_paiement', 'N/A')]
173
+ ]
174
+
175
+ payment_table = Table(payment_data, colWidths=[4*cm, 12*cm])
176
+ payment_table.setStyle(TableStyle([
177
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
178
+ ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
179
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
180
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
181
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
182
+ ('LEFTPADDING', (0, 0), (-1, -1), 0),
183
+ ('RIGHTPADDING', (0, 0), (-1, -1), 0),
184
+ ]))
185
+ elements.append(payment_table)
186
+ elements.append(Spacer(1, 0.5*cm))
187
+
188
+ # === DÉCOMPOSITION DU MONTANT ===
189
+ elements.append(Paragraph("DÉCOMPOSITION DU MONTANT", subtitle_style))
190
+
191
+ decomp_data = [
192
+ ['Principal remboursé', f"{paiement.get('montant_principal', 0):,.0f} XOF"],
193
+ ['Intérêts payés', f"{paiement.get('montant_interets', 0):,.0f} XOF"],
194
+ ['Pénalités de retard', f"{paiement.get('penalites_retard', 0):,.0f} XOF"]
195
+ ]
196
+
197
+ decomp_table = Table(decomp_data, colWidths=[10*cm, 6*cm])
198
+ decomp_table.setStyle(TableStyle([
199
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
200
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
201
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
202
+ ('ALIGN', (1, 0), (1, -1), 'RIGHT'),
203
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
204
+ ('LINEBELOW', (0, 0), (-1, -2), 0.5, colors.grey),
205
+ ('LEFTPADDING', (0, 0), (-1, -1), 5),
206
+ ('RIGHTPADDING', (0, 0), (-1, -1), 5),
207
+ ]))
208
+ elements.append(decomp_table)
209
+ elements.append(Spacer(1, 0.3*cm))
210
+
211
+ # === MONTANT TOTAL VERSÉ ===
212
+ montant_verse = paiement.get('montant_verse', paiement.get('montant_principal', 0) + paiement.get('montant_interets', 0) + paiement.get('penalites_retard', 0))
213
+
214
+ total_data = [
215
+ ['MONTANT TOTAL VERSÉ', f"{montant_verse:,.0f} XOF"]
216
+ ]
217
+
218
+ total_table = Table(total_data, colWidths=[10*cm, 6*cm])
219
+ total_table.setStyle(TableStyle([
220
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
221
+ ('FONTSIZE', (0, 0), (-1, -1), 12),
222
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.black),
223
+ ('TEXTCOLOR', (1, 0), (1, -1), colors.HexColor('#54bd4b')),
224
+ ('ALIGN', (1, 0), (1, -1), 'RIGHT'),
225
+ ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#e8f5e9')),
226
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
227
+ ('LINEABOVE', (0, 0), (-1, 0), 2, colors.HexColor('#54bd4b')),
228
+ ('LEFTPADDING', (0, 0), (-1, -1), 5),
229
+ ('RIGHTPADDING', (0, 0), (-1, -1), 5),
230
+ ('TOPPADDING', (0, 0), (-1, -1), 8),
231
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
232
+ ]))
233
+ elements.append(total_table)
234
+ elements.append(Spacer(1, 0.5*cm))
235
+
236
+ # === SOLDES ===
237
+ elements.append(Paragraph("SOLDES", subtitle_style))
238
+
239
+ soldes_data = [
240
+ ['Solde avant paiement', f"{paiement.get('solde_avant', 0):,.0f} XOF"],
241
+ ['Solde après paiement', f"{paiement.get('solde_apres', 0):,.0f} XOF"]
242
+ ]
243
+
244
+ soldes_table = Table(soldes_data, colWidths=[10*cm, 6*cm])
245
+ soldes_table.setStyle(TableStyle([
246
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
247
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
248
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
249
+ ('ALIGN', (1, 0), (1, -1), 'RIGHT'),
250
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
251
+ ('LEFTPADDING', (0, 0), (-1, -1), 5),
252
+ ('RIGHTPADDING', (0, 0), (-1, -1), 5),
253
+ ]))
254
+ elements.append(soldes_table)
255
+ elements.append(Spacer(1, 0.5*cm))
256
+
257
+ # === MOYEN DE PAIEMENT ===
258
+ elements.append(Paragraph("MOYEN DE PAIEMENT", subtitle_style))
259
+
260
+ moyen_data = [
261
+ ['Mode de paiement:', data.get('moyen', 'N/A')],
262
+ ['Référence externe:', data.get('reference', 'N/A')],
263
+ ['Transaction ID:', data.get('trans_id', 'N/A')]
264
+ ]
265
+
266
+ moyen_table = Table(moyen_data, colWidths=[4*cm, 12*cm])
267
+ moyen_table.setStyle(TableStyle([
268
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
269
+ ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
270
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
271
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
272
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
273
+ ('LEFTPADDING', (0, 0), (-1, -1), 0),
274
+ ('RIGHTPADDING', (0, 0), (-1, -1), 0),
275
+ ]))
276
+ elements.append(moyen_table)
277
+ elements.append(Spacer(1, 1*cm))
278
+
279
+ # === FOOTER ===
280
+ elements.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
281
+ elements.append(Spacer(1, 0.3*cm))
282
+
283
+ footer_text = f"Reçu généré le {datetime.now().strftime('%d/%m/%Y à %H:%M')} | Document officiel"
284
+ footer_style = ParagraphStyle(
285
+ 'Footer',
286
+ parent=styles['Normal'],
287
+ fontSize=8,
288
+ textColor=colors.grey,
289
+ alignment=TA_CENTER
290
+ )
291
+ elements.append(Paragraph(footer_text, footer_style))
292
+
293
+ # === SIGNATURE ===
294
+ elements.append(Spacer(1, 1*cm))
295
+
296
+ signature_data = [
297
+ ['Signature du Prêteur:', ''],
298
+ ['', ''],
299
+ ['', '_________________________']
300
+ ]
301
+
302
+ signature_table = Table(signature_data, colWidths=[8*cm, 8*cm])
303
+ signature_table.setStyle(TableStyle([
304
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
305
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
306
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'),
307
+ ('ALIGN', (1, 0), (1, -1), 'CENTER'),
308
+ ]))
309
+ elements.append(signature_table)
310
+
311
+ # Construction du PDF
312
+ doc.build(elements)
313
+
314
+ # Retour du buffer
315
+ buffer.seek(0)
316
+ return buffer
317
+
318
+ except Exception as e:
319
+ print(f"Erreur lors de la génération du reçu : {e}")
320
+ return None