Spaces:
Running
Running
Update src/modules/repayments.py
Browse files- src/modules/repayments.py +79 -118
src/modules/repayments.py
CHANGED
|
@@ -598,10 +598,10 @@ def show_repayments_module(client, sheet_name):
|
|
| 598 |
# --- 5. SAISIE DU PAIEMENT ---
|
| 599 |
st.divider()
|
| 600 |
st.subheader("5. Enregistrement du Paiement")
|
| 601 |
-
|
| 602 |
with st.form("repayment_form", clear_on_submit=True):
|
| 603 |
col_a, col_b = st.columns(2)
|
| 604 |
-
|
| 605 |
with col_a:
|
| 606 |
montant_verse = st.number_input(
|
| 607 |
"Montant Versé (XOF)",
|
|
@@ -610,28 +610,28 @@ def show_repayments_module(client, sheet_name):
|
|
| 610 |
value=int(st.session_state.get('selected_scenario', {}).get('total_a_encaisser', echeance_info['montant_ajuste'])),
|
| 611 |
help="Montant exact reçu"
|
| 612 |
)
|
| 613 |
-
|
| 614 |
with col_b:
|
| 615 |
moyen = st.selectbox(
|
| 616 |
"Moyen de Paiement",
|
| 617 |
["Espèces", "Mobile Money (Wave)", "Mobile Money (Orange Money)", "Virement Bancaire", "Chèque"],
|
| 618 |
help="Canal de réception des fonds"
|
| 619 |
)
|
| 620 |
-
|
| 621 |
reference_externe = st.text_input(
|
| 622 |
"Référence Transaction",
|
| 623 |
placeholder="Ex: WAVE-TXN-123456, Numéro de chèque...",
|
| 624 |
help="Référence externe pour traçabilité"
|
| 625 |
)
|
| 626 |
-
|
| 627 |
commentaire = st.text_area(
|
| 628 |
"Commentaire",
|
| 629 |
placeholder="Notes additionnelles sur la transaction...",
|
| 630 |
help="Informations complémentaires"
|
| 631 |
)
|
| 632 |
-
|
| 633 |
submit = st.form_submit_button("✅ VALIDER L'ENCAISSEMENT", use_container_width=True)
|
| 634 |
-
|
| 635 |
if submit:
|
| 636 |
if montant_verse <= 0:
|
| 637 |
st.error("❌ Le montant versé doit être supérieur à 0 XOF")
|
|
@@ -639,7 +639,7 @@ def show_repayments_module(client, sheet_name):
|
|
| 639 |
try:
|
| 640 |
# Récupération du taux de pénalité sélectionné
|
| 641 |
taux_penalite = st.session_state.get('selected_scenario', {}).get('taux', 0.0) / 100
|
| 642 |
-
|
| 643 |
# Analyse complète
|
| 644 |
analyse_complete = analyser.analyser_remboursement_complet(
|
| 645 |
loan_id,
|
|
@@ -647,7 +647,7 @@ def show_repayments_module(client, sheet_name):
|
|
| 647 |
montant_verse,
|
| 648 |
taux_penalite=taux_penalite
|
| 649 |
)
|
| 650 |
-
|
| 651 |
if 'error' in analyse_complete:
|
| 652 |
st.error(f"❌ {analyse_complete['error']}")
|
| 653 |
else:
|
|
@@ -656,19 +656,19 @@ def show_repayments_module(client, sheet_name):
|
|
| 656 |
next_id = len(existing_remb)
|
| 657 |
trans_id = f"TRX-2026-{next_id:04d}"
|
| 658 |
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 659 |
-
|
| 660 |
# Génération Numéro Reçu
|
| 661 |
annee_actuelle = datetime.now().year
|
| 662 |
count_annee = len([r for r in existing_remb if f"REC-{annee_actuelle}" in str(r)])
|
| 663 |
numero_recu = f"REC-{annee_actuelle}-{count_annee + 1:04d}"
|
| 664 |
-
|
| 665 |
# 1. Écriture dans Remboursements (avec conversions explicites)
|
| 666 |
new_row = [
|
| 667 |
str(trans_id),
|
| 668 |
str(loan_id),
|
| 669 |
str(analyse_complete['id_client']),
|
| 670 |
str(date_paiement),
|
| 671 |
-
int(montant_verse),
|
| 672 |
int(analyse_complete['montant_principal']),
|
| 673 |
int(analyse_complete['montant_interets']),
|
| 674 |
int(analyse_complete['penalites_retard']),
|
|
@@ -686,7 +686,7 @@ def show_repayments_module(client, sheet_name):
|
|
| 686 |
str(timestamp)
|
| 687 |
]
|
| 688 |
ws_remb.append_row(new_row)
|
| 689 |
-
|
| 690 |
# 2. Gestion des ajustements si PARTIEL
|
| 691 |
if analyse_complete['statut_paiement'] == "PARTIEL" and analyse_complete['montant_a_reporter'] > 0:
|
| 692 |
if analyse_complete['prochaine_echeance']:
|
|
@@ -697,9 +697,9 @@ def show_repayments_module(client, sheet_name):
|
|
| 697 |
ws_ajust = sh.add_worksheet(title="Ajustements_Echeances", rows="1000", cols="7")
|
| 698 |
ws_ajust.append_row(["ID_Ajustement", "ID_Pret", "Numero_Echeance", "Montant_Additionnel", "Raison", "Date_Creation", "Timestamp"])
|
| 699 |
next_ajust_id = 1
|
| 700 |
-
|
| 701 |
ajust_id = f"ADJ-{annee_actuelle}-{next_ajust_id:04d}"
|
| 702 |
-
|
| 703 |
ajust_row = [
|
| 704 |
str(ajust_id),
|
| 705 |
str(loan_id),
|
|
@@ -710,52 +710,46 @@ def show_repayments_module(client, sheet_name):
|
|
| 710 |
str(timestamp)
|
| 711 |
]
|
| 712 |
ws_ajust.append_row(ajust_row)
|
| 713 |
-
|
| 714 |
st.warning(f"⚠️ Paiement PARTIEL détecté. {analyse_complete['montant_a_reporter']:,.0f} XOF reportés sur l'échéance #{analyse_complete['prochaine_echeance']}")
|
| 715 |
-
|
| 716 |
-
#
|
| 717 |
if analyse_complete['doit_cloturer']:
|
| 718 |
-
# Déterminer si le prêt est UPDATED
|
| 719 |
if is_updated and ws_prets_update is not None:
|
| 720 |
-
# Mise à jour dans Prets_Update
|
| 721 |
try:
|
| 722 |
cell = ws_prets_update.find(loan_id)
|
| 723 |
header_update = ws_prets_update.row_values(1)
|
| 724 |
col_statut_idx = header_update.index("Statut") + 1
|
| 725 |
ws_prets_update.update_cell(cell.row, col_statut_idx, "TERMINE")
|
| 726 |
-
|
| 727 |
-
# Mise à jour de Date_Fin si la colonne existe
|
| 728 |
if "Date_Fin" in header_update:
|
| 729 |
col_fin_idx = header_update.index("Date_Fin") + 1
|
| 730 |
ws_prets_update.update_cell(cell.row, col_fin_idx, str(date.today()))
|
| 731 |
-
|
| 732 |
st.info(f"📝 Statut mis à jour dans Prets_Update (Version {version_info})")
|
| 733 |
except Exception as e:
|
| 734 |
st.warning(f"⚠️ Impossible de mettre à jour Prets_Update : {e}")
|
| 735 |
else:
|
| 736 |
-
# Mise à jour dans Prets_Master
|
| 737 |
try:
|
| 738 |
cell = ws_prets.find(loan_id)
|
| 739 |
header = ws_prets.row_values(1)
|
| 740 |
col_statut_idx = header.index("Statut") + 1
|
| 741 |
ws_prets.update_cell(cell.row, col_statut_idx, "TERMINE")
|
| 742 |
-
|
| 743 |
-
# Mise à jour de Date_Cloture si existe
|
| 744 |
if "Date_Cloture" in header:
|
| 745 |
col_cloture_idx = header.index("Date_Cloture") + 1
|
| 746 |
ws_prets.update_cell(cell.row, col_cloture_idx, str(date.today()))
|
| 747 |
-
|
| 748 |
st.info(f"📝 Statut mis à jour dans Prets_Master")
|
| 749 |
except Exception as e:
|
| 750 |
st.warning(f"⚠️ Impossible de mettre à jour Prets_Master : {e}")
|
| 751 |
-
|
| 752 |
-
# Message de clôture
|
| 753 |
critere_messages = {
|
| 754 |
'SOLDE_ZERO': 'Solde intégralement remboursé',
|
| 755 |
'TOUTES_ECHEANCES': 'Toutes les échéances payées',
|
| 756 |
'MONTANT_TOTAL': 'Montant total du prêt remboursé'
|
| 757 |
}
|
| 758 |
-
|
| 759 |
st.markdown(f"""
|
| 760 |
<div class="repayment-success-badge">
|
| 761 |
<h3>✅ DOSSIER {loan_id} AUTOMATIQUEMENT CLÔTURÉ</h3>
|
|
@@ -765,27 +759,25 @@ def show_repayments_module(client, sheet_name):
|
|
| 765 |
</p>
|
| 766 |
</div>
|
| 767 |
""", unsafe_allow_html=True)
|
| 768 |
-
|
| 769 |
st.info(f"Détails : {analyse_complete['message_cloture']}")
|
| 770 |
else:
|
| 771 |
st.success(f"✅ Paiement de **{montant_verse:,.0f} XOF** enregistré avec succès")
|
| 772 |
st.info(f"Référence de transaction : **{trans_id}**")
|
| 773 |
-
|
| 774 |
if analyse_complete['solde_apres'] > 0:
|
| 775 |
st.info(f"Solde restant à rembourser : **{analyse_complete['solde_apres']:,.0f} XOF**")
|
| 776 |
-
|
| 777 |
# Préparer les données pour le reçu
|
| 778 |
client_data_dict = df_clients[df_clients['ID_Client'] == analyse_complete['id_client']].iloc[0].to_dict()
|
| 779 |
|
| 780 |
-
# Convertir loan_data en dict si c'est une Series
|
| 781 |
if hasattr(loan_data, 'to_dict'):
|
| 782 |
loan_data_dict = loan_data.to_dict()
|
| 783 |
else:
|
| 784 |
loan_data_dict = dict(loan_data)
|
| 785 |
|
| 786 |
-
#
|
| 787 |
def convert_to_native(obj):
|
| 788 |
-
"""Convertit récursivement les types pandas/numpy en types Python natifs"""
|
| 789 |
import numpy as np
|
| 790 |
|
| 791 |
if isinstance(obj, dict):
|
|
@@ -803,7 +795,6 @@ def show_repayments_module(client, sheet_name):
|
|
| 803 |
else:
|
| 804 |
return obj
|
| 805 |
|
| 806 |
-
# Nettoyer toutes les données
|
| 807 |
client_data_dict = convert_to_native(client_data_dict)
|
| 808 |
loan_data_dict = convert_to_native(loan_data_dict)
|
| 809 |
analyse_complete_clean = convert_to_native(analyse_complete)
|
|
@@ -826,7 +817,7 @@ def show_repayments_module(client, sheet_name):
|
|
| 826 |
'numero_recu': numero_recu,
|
| 827 |
'loan_id': loan_id
|
| 828 |
}
|
| 829 |
-
|
| 830 |
# Récapitulatif détaillé
|
| 831 |
with st.expander("📋 Récapitulatif de la transaction", expanded=True):
|
| 832 |
recap_col1, recap_col2, recap_col3 = st.columns(3)
|
|
@@ -845,93 +836,63 @@ def show_repayments_module(client, sheet_name):
|
|
| 845 |
st.write(f"**Solde Après :** {analyse_complete['solde_apres']:,.0f} XOF")
|
| 846 |
st.write(f"**Moyen :** {moyen}")
|
| 847 |
st.write(f"**Statut :** {analyse_complete['statut_paiement']}")
|
| 848 |
-
|
| 849 |
except Exception as e:
|
| 850 |
st.error(f"❌ Erreur lors de l'enregistrement : {e}")
|
| 851 |
st.exception(e)
|
| 852 |
-
else:
|
| 853 |
-
st.error("❌ Impossible d'analyser l'échéance pour ce prêt")
|
| 854 |
-
# [Code de diagnostic existant...]
|
| 855 |
-
|
| 856 |
-
# === GÉNÉRATION DU REÇU (Section persistante) ===
|
| 857 |
-
st.divider()
|
| 858 |
-
st.subheader("📄 Génération du Reçu")
|
| 859 |
-
|
| 860 |
-
# Debug 1 : Vérifier session_state
|
| 861 |
-
st.write("🔍 DEBUG - Clés dans session_state :", list(st.session_state.keys()))
|
| 862 |
-
|
| 863 |
-
if 'derniere_transaction' in st.session_state:
|
| 864 |
-
trans_data = st.session_state['derniere_transaction']
|
| 865 |
-
|
| 866 |
-
st.success(f"✅ Transaction **{trans_data['trans_id']}** trouvée dans session_state")
|
| 867 |
-
st.caption(f"Reçu N° : **{trans_data['numero_recu']}**")
|
| 868 |
-
|
| 869 |
-
# Debug 2 : Afficher les données
|
| 870 |
-
with st.expander("🔍 Données pour le reçu (Debug)", expanded=False):
|
| 871 |
-
st.json({
|
| 872 |
-
'trans_id': trans_data.get('trans_id'),
|
| 873 |
-
'numero_recu': trans_data.get('numero_recu'),
|
| 874 |
-
'loan_id': trans_data.get('loan_id'),
|
| 875 |
-
'recu_data_keys': list(trans_data.get('recu_data', {}).keys())
|
| 876 |
-
})
|
| 877 |
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
# Appel
|
| 895 |
-
pdf_bytes = generer_recu(trans_data['recu_data'])
|
| 896 |
-
|
| 897 |
-
st.write(f"✅ Résultat: {type(pdf_bytes)}")
|
| 898 |
-
|
| 899 |
-
if pdf_bytes:
|
| 900 |
-
st.write(f"✅ Taille PDF: {len(pdf_bytes.getvalue())} bytes")
|
| 901 |
|
| 902 |
-
|
| 903 |
-
try:
|
| 904 |
-
cell_trans = ws_remb.find(trans_data['trans_id'])
|
| 905 |
-
header_remb = ws_remb.row_values(1)
|
| 906 |
-
col_recu_idx = header_remb.index("Recu_Emis") + 1
|
| 907 |
-
ws_remb.update_cell(cell_trans.row, col_recu_idx, "OUI")
|
| 908 |
-
st.write("✅ Statut mis à jour")
|
| 909 |
-
except Exception as e_update:
|
| 910 |
-
st.warning(f"⚠️ Mise à jour statut : {e_update}")
|
| 911 |
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 935 |
|
| 936 |
else:
|
| 937 |
st.warning("⚠️ Aucune transaction enregistrée. Veuillez d'abord effectuer un remboursement.")
|
|
|
|
| 598 |
# --- 5. SAISIE DU PAIEMENT ---
|
| 599 |
st.divider()
|
| 600 |
st.subheader("5. Enregistrement du Paiement")
|
| 601 |
+
|
| 602 |
with st.form("repayment_form", clear_on_submit=True):
|
| 603 |
col_a, col_b = st.columns(2)
|
| 604 |
+
|
| 605 |
with col_a:
|
| 606 |
montant_verse = st.number_input(
|
| 607 |
"Montant Versé (XOF)",
|
|
|
|
| 610 |
value=int(st.session_state.get('selected_scenario', {}).get('total_a_encaisser', echeance_info['montant_ajuste'])),
|
| 611 |
help="Montant exact reçu"
|
| 612 |
)
|
| 613 |
+
|
| 614 |
with col_b:
|
| 615 |
moyen = st.selectbox(
|
| 616 |
"Moyen de Paiement",
|
| 617 |
["Espèces", "Mobile Money (Wave)", "Mobile Money (Orange Money)", "Virement Bancaire", "Chèque"],
|
| 618 |
help="Canal de réception des fonds"
|
| 619 |
)
|
| 620 |
+
|
| 621 |
reference_externe = st.text_input(
|
| 622 |
"Référence Transaction",
|
| 623 |
placeholder="Ex: WAVE-TXN-123456, Numéro de chèque...",
|
| 624 |
help="Référence externe pour traçabilité"
|
| 625 |
)
|
| 626 |
+
|
| 627 |
commentaire = st.text_area(
|
| 628 |
"Commentaire",
|
| 629 |
placeholder="Notes additionnelles sur la transaction...",
|
| 630 |
help="Informations complémentaires"
|
| 631 |
)
|
| 632 |
+
|
| 633 |
submit = st.form_submit_button("✅ VALIDER L'ENCAISSEMENT", use_container_width=True)
|
| 634 |
+
|
| 635 |
if submit:
|
| 636 |
if montant_verse <= 0:
|
| 637 |
st.error("❌ Le montant versé doit être supérieur à 0 XOF")
|
|
|
|
| 639 |
try:
|
| 640 |
# Récupération du taux de pénalité sélectionné
|
| 641 |
taux_penalite = st.session_state.get('selected_scenario', {}).get('taux', 0.0) / 100
|
| 642 |
+
|
| 643 |
# Analyse complète
|
| 644 |
analyse_complete = analyser.analyser_remboursement_complet(
|
| 645 |
loan_id,
|
|
|
|
| 647 |
montant_verse,
|
| 648 |
taux_penalite=taux_penalite
|
| 649 |
)
|
| 650 |
+
|
| 651 |
if 'error' in analyse_complete:
|
| 652 |
st.error(f"❌ {analyse_complete['error']}")
|
| 653 |
else:
|
|
|
|
| 656 |
next_id = len(existing_remb)
|
| 657 |
trans_id = f"TRX-2026-{next_id:04d}"
|
| 658 |
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 659 |
+
|
| 660 |
# Génération Numéro Reçu
|
| 661 |
annee_actuelle = datetime.now().year
|
| 662 |
count_annee = len([r for r in existing_remb if f"REC-{annee_actuelle}" in str(r)])
|
| 663 |
numero_recu = f"REC-{annee_actuelle}-{count_annee + 1:04d}"
|
| 664 |
+
|
| 665 |
# 1. Écriture dans Remboursements (avec conversions explicites)
|
| 666 |
new_row = [
|
| 667 |
str(trans_id),
|
| 668 |
str(loan_id),
|
| 669 |
str(analyse_complete['id_client']),
|
| 670 |
str(date_paiement),
|
| 671 |
+
int(montant_verse),
|
| 672 |
int(analyse_complete['montant_principal']),
|
| 673 |
int(analyse_complete['montant_interets']),
|
| 674 |
int(analyse_complete['penalites_retard']),
|
|
|
|
| 686 |
str(timestamp)
|
| 687 |
]
|
| 688 |
ws_remb.append_row(new_row)
|
| 689 |
+
|
| 690 |
# 2. Gestion des ajustements si PARTIEL
|
| 691 |
if analyse_complete['statut_paiement'] == "PARTIEL" and analyse_complete['montant_a_reporter'] > 0:
|
| 692 |
if analyse_complete['prochaine_echeance']:
|
|
|
|
| 697 |
ws_ajust = sh.add_worksheet(title="Ajustements_Echeances", rows="1000", cols="7")
|
| 698 |
ws_ajust.append_row(["ID_Ajustement", "ID_Pret", "Numero_Echeance", "Montant_Additionnel", "Raison", "Date_Creation", "Timestamp"])
|
| 699 |
next_ajust_id = 1
|
| 700 |
+
|
| 701 |
ajust_id = f"ADJ-{annee_actuelle}-{next_ajust_id:04d}"
|
| 702 |
+
|
| 703 |
ajust_row = [
|
| 704 |
str(ajust_id),
|
| 705 |
str(loan_id),
|
|
|
|
| 710 |
str(timestamp)
|
| 711 |
]
|
| 712 |
ws_ajust.append_row(ajust_row)
|
| 713 |
+
|
| 714 |
st.warning(f"⚠️ Paiement PARTIEL détecté. {analyse_complete['montant_a_reporter']:,.0f} XOF reportés sur l'échéance #{analyse_complete['prochaine_echeance']}")
|
| 715 |
+
|
| 716 |
+
# 3. CLÔTURE AUTOMATIQUE (GESTION UPDATED)
|
| 717 |
if analyse_complete['doit_cloturer']:
|
|
|
|
| 718 |
if is_updated and ws_prets_update is not None:
|
|
|
|
| 719 |
try:
|
| 720 |
cell = ws_prets_update.find(loan_id)
|
| 721 |
header_update = ws_prets_update.row_values(1)
|
| 722 |
col_statut_idx = header_update.index("Statut") + 1
|
| 723 |
ws_prets_update.update_cell(cell.row, col_statut_idx, "TERMINE")
|
| 724 |
+
|
|
|
|
| 725 |
if "Date_Fin" in header_update:
|
| 726 |
col_fin_idx = header_update.index("Date_Fin") + 1
|
| 727 |
ws_prets_update.update_cell(cell.row, col_fin_idx, str(date.today()))
|
| 728 |
+
|
| 729 |
st.info(f"📝 Statut mis à jour dans Prets_Update (Version {version_info})")
|
| 730 |
except Exception as e:
|
| 731 |
st.warning(f"⚠️ Impossible de mettre à jour Prets_Update : {e}")
|
| 732 |
else:
|
|
|
|
| 733 |
try:
|
| 734 |
cell = ws_prets.find(loan_id)
|
| 735 |
header = ws_prets.row_values(1)
|
| 736 |
col_statut_idx = header.index("Statut") + 1
|
| 737 |
ws_prets.update_cell(cell.row, col_statut_idx, "TERMINE")
|
| 738 |
+
|
|
|
|
| 739 |
if "Date_Cloture" in header:
|
| 740 |
col_cloture_idx = header.index("Date_Cloture") + 1
|
| 741 |
ws_prets.update_cell(cell.row, col_cloture_idx, str(date.today()))
|
| 742 |
+
|
| 743 |
st.info(f"📝 Statut mis à jour dans Prets_Master")
|
| 744 |
except Exception as e:
|
| 745 |
st.warning(f"⚠️ Impossible de mettre à jour Prets_Master : {e}")
|
| 746 |
+
|
|
|
|
| 747 |
critere_messages = {
|
| 748 |
'SOLDE_ZERO': 'Solde intégralement remboursé',
|
| 749 |
'TOUTES_ECHEANCES': 'Toutes les échéances payées',
|
| 750 |
'MONTANT_TOTAL': 'Montant total du prêt remboursé'
|
| 751 |
}
|
| 752 |
+
|
| 753 |
st.markdown(f"""
|
| 754 |
<div class="repayment-success-badge">
|
| 755 |
<h3>✅ DOSSIER {loan_id} AUTOMATIQUEMENT CLÔTURÉ</h3>
|
|
|
|
| 759 |
</p>
|
| 760 |
</div>
|
| 761 |
""", unsafe_allow_html=True)
|
| 762 |
+
|
| 763 |
st.info(f"Détails : {analyse_complete['message_cloture']}")
|
| 764 |
else:
|
| 765 |
st.success(f"✅ Paiement de **{montant_verse:,.0f} XOF** enregistré avec succès")
|
| 766 |
st.info(f"Référence de transaction : **{trans_id}**")
|
| 767 |
+
|
| 768 |
if analyse_complete['solde_apres'] > 0:
|
| 769 |
st.info(f"Solde restant à rembourser : **{analyse_complete['solde_apres']:,.0f} XOF**")
|
| 770 |
+
|
| 771 |
# Préparer les données pour le reçu
|
| 772 |
client_data_dict = df_clients[df_clients['ID_Client'] == analyse_complete['id_client']].iloc[0].to_dict()
|
| 773 |
|
|
|
|
| 774 |
if hasattr(loan_data, 'to_dict'):
|
| 775 |
loan_data_dict = loan_data.to_dict()
|
| 776 |
else:
|
| 777 |
loan_data_dict = dict(loan_data)
|
| 778 |
|
| 779 |
+
# Conversion des types pandas
|
| 780 |
def convert_to_native(obj):
|
|
|
|
| 781 |
import numpy as np
|
| 782 |
|
| 783 |
if isinstance(obj, dict):
|
|
|
|
| 795 |
else:
|
| 796 |
return obj
|
| 797 |
|
|
|
|
| 798 |
client_data_dict = convert_to_native(client_data_dict)
|
| 799 |
loan_data_dict = convert_to_native(loan_data_dict)
|
| 800 |
analyse_complete_clean = convert_to_native(analyse_complete)
|
|
|
|
| 817 |
'numero_recu': numero_recu,
|
| 818 |
'loan_id': loan_id
|
| 819 |
}
|
| 820 |
+
|
| 821 |
# Récapitulatif détaillé
|
| 822 |
with st.expander("📋 Récapitulatif de la transaction", expanded=True):
|
| 823 |
recap_col1, recap_col2, recap_col3 = st.columns(3)
|
|
|
|
| 836 |
st.write(f"**Solde Après :** {analyse_complete['solde_apres']:,.0f} XOF")
|
| 837 |
st.write(f"**Moyen :** {moyen}")
|
| 838 |
st.write(f"**Statut :** {analyse_complete['statut_paiement']}")
|
| 839 |
+
|
| 840 |
except Exception as e:
|
| 841 |
st.error(f"❌ Erreur lors de l'enregistrement : {e}")
|
| 842 |
st.exception(e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 843 |
|
| 844 |
+
# === GÉNÉRATION DU REÇU (EN DEHORS DU FORMULAIRE) ===
|
| 845 |
+
if 'derniere_transaction' in st.session_state:
|
| 846 |
+
st.divider()
|
| 847 |
+
st.subheader("📄 Génération du Reçu")
|
| 848 |
|
| 849 |
+
trans_data = st.session_state['derniere_transaction']
|
| 850 |
+
|
| 851 |
+
st.success(f"✅ Transaction **{trans_data['trans_id']}** enregistrée avec succès")
|
| 852 |
+
st.caption(f"Reçu N° : **{trans_data['numero_recu']}**")
|
| 853 |
+
|
| 854 |
+
col_btn1, col_btn2 = st.columns(2)
|
| 855 |
+
|
| 856 |
+
with col_btn1:
|
| 857 |
+
if st.button("📥 Générer et Télécharger le Reçu PDF", type="primary"):
|
| 858 |
+
try:
|
| 859 |
+
from DocumentGen.InvoiceRepayment import generer_recu
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
|
| 861 |
+
pdf_bytes = generer_recu(trans_data['recu_data'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
|
| 863 |
+
if pdf_bytes:
|
| 864 |
+
# Mise à jour du statut
|
| 865 |
+
try:
|
| 866 |
+
cell_trans = ws_remb.find(trans_data['trans_id'])
|
| 867 |
+
header_remb = ws_remb.row_values(1)
|
| 868 |
+
col_recu_idx = header_remb.index("Recu_Emis") + 1
|
| 869 |
+
ws_remb.update_cell(cell_trans.row, col_recu_idx, "OUI")
|
| 870 |
+
except Exception as e_update:
|
| 871 |
+
st.warning(f"⚠️ Impossible de mettre à jour le statut : {e_update}")
|
| 872 |
+
|
| 873 |
+
# Bouton de téléchargement
|
| 874 |
+
st.download_button(
|
| 875 |
+
label="💾 CLIQUER ICI POUR TÉLÉCHARGER LE PDF",
|
| 876 |
+
data=pdf_bytes,
|
| 877 |
+
file_name=f"{trans_data['numero_recu']}_{trans_data['loan_id']}.pdf",
|
| 878 |
+
mime="application/pdf"
|
| 879 |
+
)
|
| 880 |
+
st.success("✅ Reçu généré ! Cliquez sur le bouton ci-dessus pour télécharger.")
|
| 881 |
+
else:
|
| 882 |
+
st.error("❌ Erreur lors de la génération du PDF")
|
| 883 |
+
|
| 884 |
+
except Exception as e_pdf:
|
| 885 |
+
st.error(f"❌ Erreur : {e_pdf}")
|
| 886 |
+
import traceback
|
| 887 |
+
st.code(traceback.format_exc())
|
| 888 |
+
|
| 889 |
+
with col_btn2:
|
| 890 |
+
if st.button("🔄 Nouvelle Transaction"):
|
| 891 |
+
if 'derniere_transaction' in st.session_state:
|
| 892 |
+
del st.session_state['derniere_transaction']
|
| 893 |
+
if 'selected_scenario' in st.session_state:
|
| 894 |
+
del st.session_state['selected_scenario']
|
| 895 |
+
st.rerun()
|
| 896 |
|
| 897 |
else:
|
| 898 |
st.warning("⚠️ Aucune transaction enregistrée. Veuillez d'abord effectuer un remboursement.")
|