Vortex-Flux / src /DocumentGen /AutoPDFGeneration.py
jaimalklyde's picture
Claude Haiku 5.5
style(ui): emojis remplaces par des balises tactiques sobres
495d60d
Raw History Blame Contribute Delete
25.5 kB
#Ce fichier est utiliser pour generer les docs sur les contrats de dette client et garant.
from io import BytesIO
import pandas as pd
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
def format_currency(amount):
return f"{int(amount):,}".replace(",", " ")
def get_common_styles():
styles = getSampleStyleSheet()
if 'TitleCenter' not in styles:
styles.add(ParagraphStyle(name='TitleCenter', parent=styles['Heading1'], alignment=TA_CENTER, fontSize=16, spaceAfter=20))
if 'NormalJustify' not in styles:
styles.add(ParagraphStyle(name='NormalJustify', parent=styles['Normal'], alignment=TA_JUSTIFY, fontSize=11, leading=14))
if 'BoldLabel' not in styles:
styles.add(ParagraphStyle(name='BoldLabel', parent=styles['Normal'], fontName='Helvetica-Bold', fontSize=11))
if 'OffreTag' not in styles:
styles.add(ParagraphStyle(
name='OffreTag', parent=styles['Normal'],
fontName='Helvetica-Bold', fontSize=9,
textColor=colors.HexColor('#444444'),
spaceAfter=4, spaceBefore=4,
alignment=TA_LEFT
))
return styles
def _offre_table(offre, styles):
"""
Retourne un bloc Table affichant l'offre commerciale selectionnee,
a inserer dans le preambule des contrats.
"""
offre_val = offre if offre else "N/A"
data = [["OFFRE COMMERCIALE", offre_val]]
t = Table(data, colWidths=[7*cm, 8*cm])
t.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#cccccc')),
('BACKGROUND', (0, 0), (0, 0), colors.HexColor('#f0f0f0')),
('FONTNAME', (0, 0), (0, 0), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, 0), 'Helvetica-Bold'),
('TEXTCOLOR', (1, 0), (1, 0), colors.HexColor('#1a2a4a')),
('FONTSIZE', (0, 0), (-1, -1), 10),
('PADDING', (0, 0), (-1, -1), 6),
]))
return t
# ============================================================================
# SIGNATURE ELECTRONIQUE (bloc place en fin de document)
# ============================================================================
# Chaque generateur accepte `signature=None`. Sans signature : bloc manuscrit
# historique. Avec signature (dict) : bloc de signature electronique. Les PDF sont
# generes avec invariant=1 (aucun horodatage ni identifiant aleatoire) : memes
# donnees = memes octets, ce qui permet de recalculer l'empreinte d'un contrat.
#
# Cles attendues dans `signature` : id_signature, nom, role, date_utc (JJ/MM/AAAA HH:MM:SS),
# ip, id_masque, hash_document, scelle, url_verification.
def _ligne_fait_a(signature):
if signature and signature.get("date_utc"):
return f"FAIT A DAKAR, LE : {str(signature['date_utc'])[:10]}"
return "FAIT A DAKAR, LE : __________________"
def _table_parties_signature(libelle_preteur, nom_preteur, libelle_signataire, nom_signataire, preteur_signe=False):
data = [
[libelle_preteur, libelle_signataire],
[nom_preteur, nom_signataire],
["Signature electronique ci-dessous" if preteur_signe else "", "Signature electronique ci-dessous"],
]
t = Table(data, colWidths=[8*cm, 8*cm])
t.setStyle(TableStyle([('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold')]))
return t
def _bloc_signature_electronique(signature, styles):
"""Flowables du bloc de signature electronique (fin de document)."""
from xml.sax.saxutils import escape
petit = ParagraphStyle(name='SigPetit', parent=styles['Normal'], fontSize=8, leading=10)
mono = ParagraphStyle(name='SigMono', parent=styles['Normal'], fontName='Courier', fontSize=7, leading=9)
def p(texte, style=petit):
return Paragraph(escape(str(texte)), style)
lignes = [
["SIGNATURE ELECTRONIQUE", ""],
["Signataire", p(f"{signature.get('nom', '')} ({signature.get('role', '')})")],
["Mention", p("« Lu et approuve » - acceptation par case a cocher")],
["Date et heure (UTC)", p(signature.get("date_utc", ""))],
["Adresse IP", p(signature.get("ip") or "N/A")],
# Absente des signatures anterieures : leurs PDF restent identiques octet pour octet.
*([["Localisation (GPS)", p(signature["localisation"])]] if signature.get("localisation") else []),
["Verification d'identite", p(signature.get("verification") or (
f"4 derniers caracteres de la piece d'identite ({signature.get('id_masque', '')}) "
"et code a usage unique envoye par email : valides"))],
["Identifiant de signature", p(signature.get("id_signature", ""))],
["Empreinte SHA-256 du document", p(signature.get("hash_document", ""), mono)],
["Sceau d'authenticite (HMAC)", p(signature.get("scelle", ""), mono)],
["Verification", p(signature.get("url_verification", ""))],
]
t = Table(lignes, colWidths=[5*cm, 11*cm])
t.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('SPAN', (0, 0), (-1, 0)),
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1a2a4a')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BACKGROUND', (0, 1), (0, -1), colors.whitesmoke),
('FONTNAME', (0, 1), (0, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 8),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('PADDING', (0, 0), (-1, -1), 4),
]))
return [t]
# ============================================================================
# 1. CONTRAT DE PRET
# ============================================================================
def generer_contrat_pret(loan_data, client_info, df_amortissement, signature=None, signature_preteur=None):
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm,
invariant=1)
styles = get_common_styles()
story = []
offre = loan_data.get('Offre', '')
# En-tete
story.append(Paragraph(f"N° {loan_data['ID_Pret']}", styles['Normal']))
story.append(Paragraph("CONTRAT DE PRET A INTERETS", styles['TitleCenter']))
# [OK] Bloc offre commerciale juste apres le titre
story.append(Spacer(1, 6))
story.append(_offre_table(offre, styles))
story.append(Spacer(1, 10))
story.append(Paragraph("Entre les soussignes:", styles['Normal']))
story.append(Spacer(1, 10))
# I. LE PRETEUR
story.append(Paragraph("<b>I. LE PRETEUR</b>", styles['Heading3']))
lender_text = """
<b>Nom complet :</b> MOUSSIPI YA NDEME KLYDE JAIMAL<br/>
<b>Type de piece d'identite :</b> PASSEPORT<br/>
<b>Numero de la piece d'identite :</b> AA287935<br/>
<b>Pays de residence :</b> FRANCE<br/>
Ci-apres designe « le Preteur »,<br/>
D'une part,
"""
story.append(Paragraph(lender_text, styles['Normal']))
story.append(Spacer(1, 10))
# II. LE BENEFICIAIRE
story.append(Paragraph("<b>II. LE BENEFICIAIRE (EMPRUNTEUR)</b>", styles['Heading3']))
borrower_text = f"""
<b>Nom complet :</b> {client_info['Nom_Complet']}<br/>
<b>Date de naissance :</b> {client_info.get('Date_Naissance', 'N/A')}<br/>
<b>Statut professionnel :</b> {client_info['Statut_Pro']}<br/>
<b>Adresse :</b> {client_info.get('Adresse', 'N/A')}<br/>
<b>Ville de residence :</b> {client_info['Ville']}<br/>
<b>Numero de telephone :</b> {client_info.get('Telephone', 'N/A')}<br/>
<b>Type de piece d'identite :</b> {client_info.get('Type_Piece_Identite', 'N/A')}<br/>
<b>Numero de la piece d'identite :</b> {client_info.get('ID_Officiel', 'N/A')}<br/>
Ci-apres designe « l'Emprunteur »,<br/>
D'autre part,
"""
story.append(Paragraph(borrower_text, styles['Normal']))
story.append(Spacer(1, 10))
story.append(Paragraph("Il a ete convenu et arrete ce qui suit :", styles['Normal']))
story.append(Spacer(1, 15))
# ARTICLES
story.append(Paragraph("<b>ARTICLE 1 - OBJET DU CONTRAT</b>", styles['Heading4']))
story.append(Paragraph(
"Le present contrat a pour objet l'octroi par le Preteur a l'Emprunteur d'un pret d'argent "
"a interets, conformement aux dispositions du Code des Obligations Civiles et Commerciales "
"du Senegal (COCC).", styles['NormalJustify']))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 2 - CARACTERISTIQUES DU PRET</b>", styles['Heading4']))
data_carac = [
["Offre commerciale", offre if offre else "N/A"], # [OK] Offre dans le tableau
["Montant du capital", f"{format_currency(loan_data['Montant_Capital'])} XOF"],
["Motif du pret", loan_data.get('Motif', 'Personnel')],
["Taux hebdomadaire", f"{loan_data['Taux_Hebdo']} %"],
["Duree", f"{loan_data['Duree_Semaines']} semaines"],
["Date de deblocage", loan_data['Date_Deblocage']],
["Date d'echeance finale", loan_data['Date_Fin']],
]
t_carac = Table(data_carac, colWidths=[7*cm, 8*cm])
t_carac.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('BACKGROUND', (0, 0), (0, -1), colors.whitesmoke),
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#e8eef4')),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
]))
story.append(t_carac)
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 3 - TABLEAU D'AMORTISSEMENT</b>", styles['Heading4']))
data_amort = [["Date", "Versement", "Solde Restant"]]
for _, row in df_amortissement.iterrows():
versement = int(row['Versement']) if pd.notna(row['Versement']) else 0
solde = int(row['Solde_Restant']) if pd.notna(row['Solde_Restant']) else 0
data_amort.append([
row['Date'],
f"{versement:,}".replace(",", " "),
f"{solde:,}".replace(",", " ")
])
if len(data_amort) > 20:
data_amort = data_amort[:20]
data_amort.append(["...", "...", "..."])
t_amort = Table(data_amort, colWidths=[4*cm, 5*cm, 5*cm])
t_amort.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('BACKGROUND', (0, 0), (-1, 0), colors.navy),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
]))
story.append(t_amort)
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 4 - OBLIGATION DE REMBOURSEMENT</b>", styles['Heading4']))
story.append(Paragraph(
"L'Emprunteur s'engage de maniere ferme, irrevocable et inconditionnelle a rembourser "
"l'integralite des sommes dues, meme en cas de perte d'emploi, difficultes financieres, "
"maladie ou tout autre evenement personnel. AUCUNE EXCUSE OU CIRCONSTANCE NE POURRA "
"JUSTIFIER UN NON-REMBOURSEMENT.", styles['NormalJustify']))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 5 - RETARD DE REMBOURSEMENT</b>", styles['Heading4']))
story.append(Paragraph("Tout retard de paiement entraine immediatement, sans mise en demeure prealable :", styles['Normal']))
story.append(Paragraph("Des penalites de retard fixees a 5% du montant de l'echeance impayee par semaine de retard.", styles['Normal'], bulletText="•"))
story.append(Paragraph("La possibilite pour le Preteur d'exiger le paiement immediat de la totalite du solde restant du.", styles['Normal'], bulletText="•"))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 6 - DECHEANCE DU TERME</b>", styles['Heading4']))
story.append(Paragraph(
"Le Preteur pourra prononcer la decheance du terme et exiger le remboursement immediat "
"de la totalite de la dette en cas de : non-paiement d'une seule echeance, fausse "
"declaration de revenus ou d'identite, refus de cooperation de l'Emprunteur.", styles['NormalJustify']))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 7 - DROIT DE RETRACTATION</b>", styles['Heading4']))
story.append(Paragraph(
"L'Emprunteur dispose d'un delai de retractation de sept (7) jours calendaires a compter "
"de la date de deblocage effectif des fonds. La retractation doit etre notifiee par ecrit. "
"L'Emprunteur devra restituer l'integralite des fonds recus.", styles['NormalJustify']))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 8 - DROIT APPLICABLE ET JURIDICTION COMPETENTE</b>", styles['Heading4']))
story.append(Paragraph(
"Le present contrat est regi par le droit Senegalais. Tout litige releve de la competence "
"exclusive des tribunaux de Dakar.", styles['NormalJustify']))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>ARTICLE 9 - ACCEPTATION</b>", styles['Heading4']))
story.append(Paragraph(
"L'Emprunteur reconnait avoir lu, compris et accepte toutes les clauses du present contrat "
"sans contrainte ni reserve.", styles['NormalJustify']))
story.append(Spacer(1, 20))
story.append(Paragraph(_ligne_fait_a(signature), styles['Normal']))
story.append(Spacer(1, 20))
if signature:
story.append(_table_parties_signature(
"LE PRETEUR", "MOUSSIPI YA NDEME KLYDE JAIMAL",
"LE BENEFICIAIRE", f"{client_info['Nom_Complet']}", preteur_signe=bool(signature_preteur)))
story.append(Spacer(1, 10))
story.extend(_bloc_signature_electronique(signature, styles))
if signature_preteur:
story.append(Spacer(1, 10))
story.extend(_bloc_signature_electronique(signature_preteur, styles))
else:
data_sign = [
["LE PRETEUR", "LE BENEFICIAIRE"],
["MOUSSIPI YA NDEME KLYDE JAIMAL", f"{client_info['Nom_Complet']}"],
["", "Signature precedee de la mention manuscrite :"],
["Signature :", "« Lu et approuve »"],
["\n\n\n", "\n\n\n"]
]
t_sign = Table(data_sign, colWidths=[8*cm, 8*cm])
story.append(t_sign)
doc.build(story)
buffer.seek(0)
return buffer
# ============================================================================
# 2. RECONNAISSANCE DE DETTE
# ============================================================================
def generer_reconnaissance_dette(loan_data, client_info, signature=None, montant_en_lettres_auto=False):
"""`montant_en_lettres_auto` : remplit le montant en lettres sous le montant en chiffres
(snapshots version >= 2 ; les anciens restent identiques octet pour octet).
La mention « Bon pour... » n'est jamais remplie par le systeme : elle est saisie par le
signataire lui-meme sur la page de signature (signature["mention_bon_pour"])."""
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm,
invariant=1)
styles = get_common_styles()
story = []
offre = loan_data.get('Offre', '')
story.append(Paragraph("LETTRE DE RECONNAISSANCE DE DETTE", styles['TitleCenter']))
# [OK] Offre sous le titre
story.append(Spacer(1, 6))
story.append(_offre_table(offre, styles))
story.append(Spacer(1, 15))
story.append(Paragraph("Je soussigne(e) :", styles['Normal']))
info_debtor = f"""
<b>Nom et prenoms :</b> {client_info['Nom_Complet']}<br/>
<b>Date de naissance :</b> {client_info.get('Date_Naissance', '................')}<br/>
<b>Statut professionnel :</b> {client_info['Statut_Pro']}<br/>
<b>Adresse :</b> {client_info.get('Adresse', '................')}<br/>
<b>Ville de residence :</b> {client_info['Ville']}<br/>
<b>Numero de telephone :</b> {client_info.get('Telephone', '................')}<br/>
<b>Type de piece d'identite :</b> {client_info.get('Type_Piece_Identite', '................')}<br/>
<b>Numero de la piece d'identite :</b> {client_info.get('ID_Officiel', '................')}<br/>
"""
story.append(Paragraph(info_debtor, styles['Normal']))
story.append(Spacer(1, 10))
story.append(Paragraph(
"reconnais, librement, expressement et sans aucune contrainte, devoir la somme "
"ci-apres indiquee a :", styles['NormalJustify']))
story.append(Paragraph(
"<b>Monsieur MOUSSIPI YA NDEME KLYDE JAIMAL</b>, titulaire du passeport n° AA287935, "
"residant en France, ci-apres designe « le Preteur ».", styles['Normal']))
story.append(Spacer(1, 10))
story.append(Paragraph(
f"Je reconnais devoir au Preteur la somme totale de "
f"<b>{format_currency(loan_data['Montant_Total'])} FCFA</b> "
f"au titre de l'offre <b>{offre if offre else 'N/A'}</b>.", # [OK] Offre dans le corps
styles['Normal']))
if montant_en_lettres_auto:
from DocumentGen.nombres_fr import montant_en_lettres
story.append(Paragraph(f"({montant_en_lettres(loan_data['Montant_Total'])} Francs CFA).", styles['Normal']))
else:
story.append(Paragraph("(......................................................................... Francs CFA).", styles['Normal']))
story.append(Spacer(1, 10))
story.append(Paragraph(
f"Cette somme correspond au montant total a rembourser au titre du Contrat de pret a "
f"interets n° {loan_data['ID_Pret']}, incluant le capital, les interets, les penalites "
f"eventuelles et tous frais prevus audit contrat.", styles['NormalJustify']))
story.append(Spacer(1, 5))
story.append(Paragraph(
"Je m'engage de maniere ferme, irrevocable et inconditionnelle a rembourser integralement "
"ladite somme selon les modalites prevues dans le contrat de pret.", styles['NormalJustify']))
story.append(Spacer(1, 5))
story.append(Paragraph(
"Je reconnais expressement qu'aucune difficulte personnelle, professionnelle, financiere "
"ou medicale, aucune perte d'emploi ou baisse de revenus, ne saurait en aucun cas justifier "
"un retard ou un refus de paiement.", styles['NormalJustify']))
story.append(Spacer(1, 5))
story.append(Paragraph(
"En cas de non-respect de mes obligations, le Preteur pourra exiger le paiement immediat "
"de la totalite des sommes restant dues, appliquer les penalites de retard prevues au "
"contrat de pret et engager toute procedure de recouvrement.", styles['NormalJustify']))
story.append(Spacer(1, 5))
story.append(Paragraph(
"Je reconnais avoir lu, compris et accepte l'integralite des engagements resultant de la "
"presente reconnaissance de dette, que je signe en toute connaissance de cause.", styles['NormalJustify']))
story.append(Spacer(1, 20))
story.append(Paragraph(_ligne_fait_a(signature), styles['Normal']))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>L'EMPRUNTEUR (DEBITEUR)</b>", styles['Normal']))
story.append(Paragraph(f"Nom : {client_info['Nom_Complet']}", styles['Normal']))
story.append(Spacer(1, 5))
mention = str((signature or {}).get("mention_bon_pour", "") or "").strip()
if mention:
from xml.sax.saxutils import escape
story.append(Paragraph(f"<i>« {escape(mention)} »</i>", styles['Normal']))
story.append(Paragraph("<font size=7>(mention saisie par le signataire lui-meme lors de la "
"signature electronique)</font>", styles['Normal']))
else:
story.append(Paragraph(
"<i>« Bon pour reconnaissance de dette de la somme de "
"......................... Francs CFA, que je m'engage a rembourser integralement »</i>",
styles['Normal']))
story.append(Spacer(1, 15))
if signature:
story.extend(_bloc_signature_electronique(signature, styles))
else:
story.append(Paragraph("Signature :", styles['Normal']))
doc.build(story)
buffer.seek(0)
return buffer
# ============================================================================
# 3. CONTRAT DE CAUTION
# ============================================================================
def generer_contrat_caution(loan_data, garant_info, signature=None, signature_preteur=None):
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm,
invariant=1)
styles = get_common_styles()
story = []
offre = loan_data.get('Offre', '')
story.append(Paragraph("CONTRAT DE CAUTION SOLIDAIRE", styles['TitleCenter']))
story.append(Paragraph(
f"Relatif au Contrat de pret a interets n° {loan_data['ID_Pret']}",
styles['TitleCenter']))
# [OK] Offre sous le titre
story.append(Spacer(1, 6))
story.append(_offre_table(offre, styles))
story.append(Spacer(1, 10))
story.append(Paragraph("Entre les soussignes :", styles['Normal']))
story.append(Paragraph("<b>I. LE PRETEUR</b>", styles['Heading3']))
story.append(Paragraph(
"Nom complet : MOUSSIPI YA NDEME KLYDE JAIMAL<br/>"
"Numero piece : AA287935 (Passeport)<br/>"
"Pays : FRANCE<br/>"
"Ci-apres designe « le Preteur »", styles['Normal']))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>II. LE GARANT (CAUTION)</b>", styles['Heading3']))
garant_txt = f"""
Nom complet : {garant_info['Nom_Complet']}<br/>
Statut professionnel : {garant_info.get('Statut_Pro', 'N/A')}<br/>
Ville : {garant_info.get('Ville', 'N/A')}<br/>
Telephone : {garant_info.get('Telephone', 'N/A')}<br/>
Ci-apres designe « le Garant »,
"""
story.append(Paragraph(garant_txt, styles['Normal']))
story.append(Spacer(1, 15))
story.append(Paragraph("<b>ARTICLE 1 - OBJET DU CONTRAT</b>", styles['Heading4']))
story.append(Paragraph(
f"Le Garant s'engage a garantir l'execution integrale des obligations de remboursement "
f"de l'Emprunteur, beneficiaire du Contrat de pret n° {loan_data['ID_Pret']} "
f"souscrit dans le cadre de l'offre <b>{offre if offre else 'N/A'}</b>.", # [OK] Offre dans l'objet
styles['NormalJustify']))
story.append(Paragraph("<b>ARTICLE 2 - MONTANT ET ETENDUE</b>", styles['Heading4']))
story.append(Paragraph(
"La garantie couvre l'integralite des sommes dues : capital, interets, penalites de retard. "
"L'engagement est illimite jusqu'au paiement total.", styles['NormalJustify']))
story.append(Paragraph("<b>ARTICLE 3 - NATURE DE L'ENGAGEMENT</b>", styles['Heading4']))
story.append(Paragraph(
"Le Garant se porte caution solidaire et renonce expressement au benefice de discussion "
"et au benefice de division. Le Preteur pourra reclamer directement au Garant le paiement "
"sans mise en demeure prealable de l'Emprunteur.", styles['NormalJustify']))
story.append(Paragraph("<b>ARTICLE 4 - EXIGIBILITE IMMEDIATE</b>", styles['Heading4']))
story.append(Paragraph(
"En cas de non-remboursement d'une seule echeance par l'Emprunteur, le Preteur pourra "
"exiger immediatement du Garant le paiement de la totalite des sommes dues.", styles['NormalJustify']))
story.append(Paragraph("<b>ARTICLES 5 A 9</b>", styles['Heading4']))
story.append(Paragraph(
"L'engagement demeure valable jusqu'au remboursement integral. Le present contrat est "
"regi par le droit senegalais (Tribunaux de Dakar). Le Garant reconnait avoir lu et "
"accepte toutes les clauses.", styles['NormalJustify']))
story.append(Spacer(1, 20))
story.append(Paragraph(_ligne_fait_a(signature), styles['Normal']))
if signature:
story.append(_table_parties_signature(
"LE PRETEUR", "MOUSSIPI YA NDEME K.J.",
"LE GARANT (CAUTION)", f"{garant_info['Nom_Complet']}", preteur_signe=bool(signature_preteur)))
story.append(Spacer(1, 10))
story.extend(_bloc_signature_electronique(signature, styles))
if signature_preteur:
story.append(Spacer(1, 10))
story.extend(_bloc_signature_electronique(signature_preteur, styles))
else:
data_sign = [
["LE PRETEUR", "LE GARANT (CAUTION)"],
["MOUSSIPI YA NDEME K.J.", f"{garant_info['Nom_Complet']}"],
["", "Signature precedee de la mention manuscrite :"],
["Signature :", "« Bon pour caution solidaire,\nrenonciation aux benefices de discussion »"],
["\n\n", "\n\n"]
]
t_sign = Table(data_sign, colWidths=[8*cm, 8*cm])
story.append(t_sign)
doc.build(story)
buffer.seek(0)
return buffer