diff --git a/cleaner.py b/cleaner.py index 9b4946d..49a9956 100644 --- a/cleaner.py +++ b/cleaner.py @@ -1,17 +1,33 @@ from pathlib import Path -def supprimer_commentaires_ligne(fichier: str) -> None: +def supprimer_lignes_logger(fichier: str) -> None: path = Path(fichier) lignes_filtrees = [] + skip_block = False + parentheses_balance = 0 + with path.open("r", encoding="utf-8") as f: for ligne in f: - if ligne.lstrip().startswith("logger"): + stripped = ligne.lstrip() + + # Détection du début d'un appel logger + if not skip_block and stripped.startswith("logger"): + skip_block = True + parentheses_balance += ligne.count("(") - ligne.count(")") continue + + if skip_block: + parentheses_balance += ligne.count("(") - ligne.count(")") + if parentheses_balance <= 0: + skip_block = False + parentheses_balance = 0 + continue + lignes_filtrees.append(ligne) path.write_text("".join(lignes_filtrees), encoding="utf-8") if __name__ == "__main__": - supprimer_commentaires_ligne("sage_connector.py") + supprimer_lignes_logger("sage_connector.py") diff --git a/sage_connector.py b/sage_connector.py index a6d560e..07d0b19 100644 --- a/sage_connector.py +++ b/sage_connector.py @@ -43,8 +43,6 @@ from utils.tiers.clients.clients_data import ( ) -from utils.tiers.contacts.contacts import obtenir_contact - from utils.articles.stock_check import verifier_stock_suffisant from utils.articles.articles_data_com import _extraire_article from utils.tiers.tiers_data_sql import _build_tiers_select_query @@ -53,7 +51,8 @@ from utils.functions.functions import ( _safe_strip, _safe_int, _clean_str, - _try_set_attribute + _try_set_attribute, + normaliser_date ) from utils.functions.items_to_dict import ( @@ -64,12 +63,25 @@ from utils.functions.items_to_dict import ( from utils.functions.sage_utilities import ( _verifier_devis_non_transforme, - verifier_si_deja_transforme_sql, peut_etre_transforme, lire_erreurs_sage ) -from utils.documents.devis_extraction import _extraire_infos_devis +from utils.documents.documents_data_sql import ( + _afficher_etat_document, + _compter_lignes_document, + _rechercher_devis_par_numero, + _lire_document_sql, + _lister_documents_avec_lignes_sql +) + +from utils.documents.devis.devis_extraction import _extraire_infos_devis +from utils.documents.devis.devis_check import ( + _relire_devis, + _recuperer_numero_devis +) + +from utils.tiers.contacts.contacts import _get_contacts_client logger = logging.getLogger(__name__) @@ -336,7 +348,7 @@ class SageConnector: "categorie_compta": row.N_CatCompta, } - fournisseur["contacts"] = self._get_contacts_client(row.CT_Num, conn) + fournisseur["contacts"] = _get_contacts_client(row.CT_Num, conn) fournisseurs.append(fournisseur) @@ -347,7 +359,6 @@ class SageConnector: logger.error(f" Erreur SQL fournisseurs: {e}") raise RuntimeError(f"Erreur lecture fournisseurs: {str(e)}") - def lire_fournisseur(self, code_fournisseur): try: with self._get_sql_connection() as conn: @@ -497,7 +508,7 @@ class SageConnector: "categorie_compta": row.N_CatCompta, } - fournisseur["contacts"] = self._get_contacts_client(row.CT_Num, conn) + fournisseur["contacts"] = _get_contacts_client(row.CT_Num, conn) logger.info(f" SQL: Fournisseur {code_fournisseur} avec {len(fournisseur)} champs") return fournisseur @@ -952,58 +963,6 @@ class SageConnector: raise RuntimeError(f"Erreur technique Sage: {error_message}") - def _get_contacts_client(self, numero: str, conn) -> list: - try: - cursor = conn.cursor() - - query = """ - SELECT - CT_Num, CT_No, N_Contact, - CT_Civilite, CT_Nom, CT_Prenom, CT_Fonction, - N_Service, - CT_Telephone, CT_TelPortable, CT_Telecopie, CT_EMail, - CT_Facebook, CT_LinkedIn, CT_Skype - FROM F_CONTACTT - WHERE CT_Num = ? - ORDER BY N_Contact, CT_Nom, CT_Prenom - """ - - cursor.execute(query, [numero]) - rows = cursor.fetchall() - - query_client = """ - SELECT CT_Contact - FROM F_COMPTET - WHERE CT_Num = ? - """ - cursor.execute(query_client, [numero]) - client_row = cursor.fetchone() - - nom_contact_defaut = None - if client_row: - nom_contact_defaut = _safe_strip(client_row.CT_Contact) - - contacts = [] - for row in rows: - contact = _row_to_contact_dict(row) - - if nom_contact_defaut: - nom_complet = f"{contact.get('prenom', '')} {contact['nom']}".strip() - contact["est_defaut"] = ( - nom_complet == nom_contact_defaut or - contact['nom'] == nom_contact_defaut - ) - else: - contact["est_defaut"] = False - - contacts.append(contact) - - return contacts - - except Exception as e: - logger.warning(f" Impossible de récupérer contacts pour {numero}: {e}") - return [] - def lister_tous_clients(self, filtre=""): """ Liste tous les clients avec TOUS les champs gérés par creer_client @@ -1164,7 +1123,7 @@ class SageConnector: "categorie_compta": row.N_CatCompta, } - client["contacts"] = self._get_contacts_client(row.CT_Num, conn) + client["contacts"] = _get_contacts_client(row.CT_Num, conn) clients.append(client) @@ -1328,7 +1287,7 @@ class SageConnector: "categorie_compta": row.N_CatCompta, } - client["contacts"] = self._get_contacts_client(row.CT_Num, conn) + client["contacts"] = _get_contacts_client(row.CT_Num, conn) logger.info(f" SQL: Client {code_client} avec {len(client)} champs") return client @@ -1530,842 +1489,66 @@ class SageConnector: logger.error(f" Erreur SQL articles: {e}", exc_info=True) raise RuntimeError(f"Erreur lecture articles: {str(e)}") - def _lire_document_sql(self, numero: str, type_doc: int): + def obtenir_contact(self, numero: str, contact_numero: int) -> Optional[Dict]: + """ + Récupère un contact spécifique par son CT_No + """ try: with self._get_sql_connection() as conn: cursor = conn.cursor() - + query = """ SELECT - d.DO_Piece, d.DO_Date, d.DO_Ref, d.DO_TotalHT, d.DO_TotalTTC, - d.DO_Statut, d.DO_Tiers, d.DO_DateLivr, d.DO_DateExpedition, - d.DO_Contact, d.DO_TotalHTNet, d.DO_NetAPayer, - d.DO_MontantRegle, d.DO_Reliquat, d.DO_TxEscompte, d.DO_Escompte, - d.DO_Taxe1, d.DO_Taxe2, d.DO_Taxe3, - d.DO_CodeTaxe1, d.DO_CodeTaxe2, d.DO_CodeTaxe3, - d.DO_EStatut, d.DO_Imprim, d.DO_Valide, d.DO_Cloture, - d.DO_Transfere, d.DO_Souche, d.DO_PieceOrig, d.DO_GUID, - d.CA_Num, d.CG_Num, d.DO_Expedit, d.DO_Condition, - d.DO_Tarif, d.DO_TypeFrais, d.DO_ValFrais, - d.DO_TypeFranco, d.DO_ValFranco, - c.CT_Intitule, c.CT_Adresse, c.CT_CodePostal, - c.CT_Ville, c.CT_Telephone, c.CT_EMail - FROM F_DOCENTETE d - LEFT JOIN F_COMPTET c ON d.DO_Tiers = c.CT_Num - WHERE d.DO_Piece = ? AND d.DO_Type = ? + CT_Num, CT_No, N_Contact, + CT_Civilite, CT_Nom, CT_Prenom, CT_Fonction, + N_Service, + CT_Telephone, CT_TelPortable, CT_Telecopie, CT_EMail, + CT_Facebook, CT_LinkedIn, CT_Skype + FROM F_CONTACTT + WHERE CT_Num = ? AND CT_No = ? """ - - logger.info(f"[SQL READ] Lecture directe de {numero} (type={type_doc})") - - cursor.execute(query, (numero, type_doc)) + + cursor.execute(query, [numero, contact_numero]) row = cursor.fetchone() - + if not row: - logger.warning( - f"[SQL READ] Document {numero} (type={type_doc}) INTROUVABLE dans F_DOCENTETE" - ) return None - - numero_piece = _safe_strip(row[0]) - logger.info(f"[SQL READ] Document trouvé: {numero_piece}") - - - doc = { - "numero": numero_piece, - "reference": _safe_strip(row[2]), # DO_Ref - "date": str(row[1]) if row[1] else "", # DO_Date - "date_livraison": (str(row[7]) if row[7] else ""), # DO_DateLivr - "date_expedition": ( - str(row[8]) if row[8] else "" - ), # DO_DateExpedition - "client_code": _safe_strip(row[6]), # DO_Tiers - "client_intitule": _safe_strip(row[39]), # CT_Intitule - "client_adresse": _safe_strip(row[40]), # CT_Adresse - "client_code_postal": _safe_strip(row[41]), # CT_CodePostal - "client_ville": _safe_strip(row[42]), # CT_Ville - "client_telephone": _safe_strip(row[43]), # CT_Telephone - "client_email": _safe_strip(row[44]), # CT_EMail - "contact": _safe_strip(row[9]), # DO_Contact - "total_ht": float(row[3]) if row[3] else 0.0, # DO_TotalHT - "total_ht_net": float(row[10]) if row[10] else 0.0, # DO_TotalHTNet - "total_ttc": float(row[4]) if row[4] else 0.0, # DO_TotalTTC - "net_a_payer": float(row[11]) if row[11] else 0.0, # DO_NetAPayer - "montant_regle": ( - float(row[12]) if row[12] else 0.0 - ), # DO_MontantRegle - "reliquat": float(row[13]) if row[13] else 0.0, # DO_Reliquat - "taux_escompte": ( - float(row[14]) if row[14] else 0.0 - ), # DO_TxEscompte - "escompte": float(row[15]) if row[15] else 0.0, # DO_Escompte - "taxe1": float(row[16]) if row[16] else 0.0, # DO_Taxe1 - "taxe2": float(row[17]) if row[17] else 0.0, # DO_Taxe2 - "taxe3": float(row[18]) if row[18] else 0.0, # DO_Taxe3 - "code_taxe1": _safe_strip(row[19]), # DO_CodeTaxe1 - "code_taxe2": _safe_strip(row[20]), # DO_CodeTaxe2 - "code_taxe3": _safe_strip(row[21]), # DO_CodeTaxe3 - "statut": int(row[5]) if row[5] is not None else 0, # DO_Statut - "statut_estatut": ( - int(row[22]) if row[22] is not None else 0 - ), # DO_EStatut - "imprime": int(row[23]) if row[23] is not None else 0, # DO_Imprim - "valide": int(row[24]) if row[24] is not None else 0, # DO_Valide - "cloture": int(row[25]) if row[25] is not None else 0, # DO_Cloture - "transfere": ( - int(row[26]) if row[26] is not None else 0 - ), # DO_Transfere - "souche": int(row[27]) if row[27] is not None else 0, # DO_Souche - "piece_origine": _safe_strip(row[28]), # DO_PieceOrig - "guid": _safe_strip(row[29]), # DO_GUID - "ca_num": _safe_strip(row[30]), # CA_Num - "cg_num": _safe_strip(row[31]), # CG_Num - "expedition": ( - int(row[32]) if row[32] is not None else 1 - ), # DO_Expedit - "condition": ( - int(row[33]) if row[33] is not None else 1 - ), # DO_Condition - "tarif": int(row[34]) if row[34] is not None else 1, # DO_Tarif - "type_frais": ( - int(row[35]) if row[35] is not None else 0 - ), # DO_TypeFrais - "valeur_frais": float(row[36]) if row[36] else 0.0, # DO_ValFrais - "type_franco": ( - int(row[37]) if row[37] is not None else 0 - ), # DO_TypeFranco - "valeur_franco": float(row[38]) if row[38] else 0.0, # DO_ValFranco - } - - cursor.execute( - """ - SELECT - dl.*, - a.AR_Design, a.FA_CodeFamille, a.AR_PrixTTC, a.AR_PrixVen, a.AR_PrixAch, - a.AR_Gamme1, a.AR_Gamme2, a.AR_CodeBarre, a.AR_CoutStd, - a.AR_PoidsNet, a.AR_PoidsBrut, a.AR_UniteVen, - a.AR_Type, a.AR_Nature, a.AR_Escompte, a.AR_Garantie - FROM F_DOCLIGNE dl - LEFT JOIN F_ARTICLE a ON dl.AR_Ref = a.AR_Ref - WHERE dl.DO_Piece = ? AND dl.DO_Type = ? - ORDER BY dl.DL_Ligne - """, - (numero, type_doc), - ) - - lignes = [] - for ligne_row in cursor.fetchall(): - montant_ht = ( - float(ligne_row.DL_MontantHT) if ligne_row.DL_MontantHT else 0.0 - ) - montant_net = ( - float(ligne_row.DL_MontantNet) - if hasattr(ligne_row, "DL_MontantNet") - and ligne_row.DL_MontantNet - else montant_ht - ) - - taux_taxe1 = ( - float(ligne_row.DL_Taxe1) - if hasattr(ligne_row, "DL_Taxe1") and ligne_row.DL_Taxe1 - else 0.0 - ) - taux_taxe2 = ( - float(ligne_row.DL_Taxe2) - if hasattr(ligne_row, "DL_Taxe2") and ligne_row.DL_Taxe2 - else 0.0 - ) - taux_taxe3 = ( - float(ligne_row.DL_Taxe3) - if hasattr(ligne_row, "DL_Taxe3") and ligne_row.DL_Taxe3 - else 0.0 - ) - - total_taux_taxes = taux_taxe1 + taux_taxe2 + taux_taxe3 - montant_ttc = montant_net * (1 + total_taux_taxes / 100) - - montant_taxe1 = montant_net * (taux_taxe1 / 100) - montant_taxe2 = montant_net * (taux_taxe2 / 100) - montant_taxe3 = montant_net * (taux_taxe3 / 100) - - ligne = { - "numero_ligne": ( - int(ligne_row.DL_Ligne) if ligne_row.DL_Ligne else 0 - ), - "article_code": _safe_strip(ligne_row.AR_Ref), - "designation": _safe_strip(ligne_row.DL_Design), - "designation_article": _safe_strip(ligne_row.AR_Design), - "quantite": ( - float(ligne_row.DL_Qte) if ligne_row.DL_Qte else 0.0 - ), - "quantite_livree": ( - float(ligne_row.DL_QteLiv) - if hasattr(ligne_row, "DL_QteLiv") and ligne_row.DL_QteLiv - else 0.0 - ), - "quantite_reservee": ( - float(ligne_row.DL_QteRes) - if hasattr(ligne_row, "DL_QteRes") and ligne_row.DL_QteRes - else 0.0 - ), - "unite": ( - _safe_strip(ligne_row.DL_Unite) - if hasattr(ligne_row, "DL_Unite") - else "" - ), - "prix_unitaire_ht": ( - float(ligne_row.DL_PrixUnitaire) - if ligne_row.DL_PrixUnitaire - else 0.0 - ), - "prix_unitaire_achat": ( - float(ligne_row.AR_PrixAch) if ligne_row.AR_PrixAch else 0.0 - ), - "prix_unitaire_vente": ( - float(ligne_row.AR_PrixVen) if ligne_row.AR_PrixVen else 0.0 - ), - "prix_unitaire_ttc": ( - float(ligne_row.AR_PrixTTC) if ligne_row.AR_PrixTTC else 0.0 - ), - "montant_ligne_ht": montant_ht, - "montant_ligne_net": montant_net, - "montant_ligne_ttc": montant_ttc, - "remise_valeur1": ( - float(ligne_row.DL_Remise01REM_Valeur) - if hasattr(ligne_row, "DL_Remise01REM_Valeur") - and ligne_row.DL_Remise01REM_Valeur - else 0.0 - ), - "remise_type1": ( - int(ligne_row.DL_Remise01REM_Type) - if hasattr(ligne_row, "DL_Remise01REM_Type") - and ligne_row.DL_Remise01REM_Type - else 0 - ), - "remise_valeur2": ( - float(ligne_row.DL_Remise02REM_Valeur) - if hasattr(ligne_row, "DL_Remise02REM_Valeur") - and ligne_row.DL_Remise02REM_Valeur - else 0.0 - ), - "remise_type2": ( - int(ligne_row.DL_Remise02REM_Type) - if hasattr(ligne_row, "DL_Remise02REM_Type") - and ligne_row.DL_Remise02REM_Type - else 0 - ), - "remise_article": ( - float(ligne_row.AR_Escompte) - if ligne_row.AR_Escompte - else 0.0 - ), - "taux_taxe1": taux_taxe1, - "montant_taxe1": montant_taxe1, - "taux_taxe2": taux_taxe2, - "montant_taxe2": montant_taxe2, - "taux_taxe3": taux_taxe3, - "montant_taxe3": montant_taxe3, - "total_taxes": montant_taxe1 + montant_taxe2 + montant_taxe3, - "famille_article": _safe_strip(ligne_row.FA_CodeFamille), - "gamme1": _safe_strip(ligne_row.AR_Gamme1), - "gamme2": _safe_strip(ligne_row.AR_Gamme2), - "code_barre": _safe_strip(ligne_row.AR_CodeBarre), - "type_article": _safe_strip(ligne_row.AR_Type), - "nature_article": _safe_strip(ligne_row.AR_Nature), - "garantie": _safe_strip(ligne_row.AR_Garantie), - "cout_standard": ( - float(ligne_row.AR_CoutStd) if ligne_row.AR_CoutStd else 0.0 - ), - "poids_net": ( - float(ligne_row.AR_PoidsNet) - if ligne_row.AR_PoidsNet - else 0.0 - ), - "poids_brut": ( - float(ligne_row.AR_PoidsBrut) - if ligne_row.AR_PoidsBrut - else 0.0 - ), - "unite_vente": _safe_strip(ligne_row.AR_UniteVen), - "date_livraison_ligne": ( - str(ligne_row.DL_DateLivr) - if hasattr(ligne_row, "DL_DateLivr") - and ligne_row.DL_DateLivr - else "" - ), - "statut_ligne": ( - int(ligne_row.DL_Statut) - if hasattr(ligne_row, "DL_Statut") - and ligne_row.DL_Statut is not None - else 0 - ), - "depot": ( - _safe_strip(ligne_row.DE_No) - if hasattr(ligne_row, "DE_No") - else "" - ), - "numero_commande": ( - _safe_strip(ligne_row.DL_NoColis) - if hasattr(ligne_row, "DL_NoColis") - else "" - ), - "num_colis": ( - _safe_strip(ligne_row.DL_Colis) - if hasattr(ligne_row, "DL_Colis") - else "" - ), - } - lignes.append(ligne) - - doc["lignes"] = lignes - doc["nb_lignes"] = len(lignes) - - total_ht_calcule = sum(l.get("montant_ligne_ht", 0) for l in lignes) - total_ttc_calcule = sum(l.get("montant_ligne_ttc", 0) for l in lignes) - total_taxes_calcule = sum(l.get("total_taxes", 0) for l in lignes) - - doc["total_ht_calcule"] = total_ht_calcule - doc["total_ttc_calcule"] = total_ttc_calcule - doc["total_taxes_calcule"] = total_taxes_calcule - - return doc - + + return _row_to_contact_dict(row) + except Exception as e: - logger.error(f" Erreur SQL lecture document {numero}: {e}", exc_info=True) - return None - - def _lister_documents_avec_lignes_sql( - self, - type_doc: int, - filtre: str = "", - limit: int = None, - ): - """Liste les documents avec leurs lignes.""" - try: - type_doc_sql = self._convertir_type_pour_sql(type_doc) - logger.info(f"[SQL LIST] ═══ Type COM {type_doc} → SQL {type_doc_sql} ═══") - - with self._get_sql_connection() as conn: - cursor = conn.cursor() - - query = """ - SELECT DISTINCT - d.DO_Piece, d.DO_Type, d.DO_Date, d.DO_Ref, d.DO_Tiers, - d.DO_TotalHT, d.DO_TotalTTC, d.DO_NetAPayer, d.DO_Statut, - d.DO_DateLivr, d.DO_DateExpedition, d.DO_Contact, d.DO_TotalHTNet, - d.DO_MontantRegle, d.DO_Reliquat, d.DO_TxEscompte, d.DO_Escompte, - d.DO_Taxe1, d.DO_Taxe2, d.DO_Taxe3, - d.DO_CodeTaxe1, d.DO_CodeTaxe2, d.DO_CodeTaxe3, - d.DO_EStatut, d.DO_Imprim, d.DO_Valide, d.DO_Cloture, d.DO_Transfere, - d.DO_Souche, d.DO_PieceOrig, d.DO_GUID, - d.CA_Num, d.CG_Num, d.DO_Expedit, d.DO_Condition, d.DO_Tarif, - d.DO_TypeFrais, d.DO_ValFrais, d.DO_TypeFranco, d.DO_ValFranco, - c.CT_Intitule, c.CT_Adresse, c.CT_CodePostal, - c.CT_Ville, c.CT_Telephone, c.CT_EMail - FROM F_DOCENTETE d - LEFT JOIN F_COMPTET c ON d.DO_Tiers = c.CT_Num - WHERE d.DO_Type = ? - """ - - params = [type_doc_sql] - - if filtre: - query += " AND (d.DO_Piece LIKE ? OR c.CT_Intitule LIKE ? OR d.DO_Ref LIKE ?)" - params.extend([f"%{filtre}%", f"%{filtre}%", f"%{filtre}%"]) - - query += " ORDER BY d.DO_Date DESC" - - if limit: - query = f"SELECT TOP ({limit}) * FROM ({query}) AS subquery" - - cursor.execute(query, params) - entetes = cursor.fetchall() - - logger.info(f"[SQL LIST] {len(entetes)} documents SQL") - - documents = [] - stats = { - "total": len(entetes), - "exclus_prefixe": 0, - "erreur_construction": 0, - "erreur_lignes": 0, - "erreur_transformations": 0, - "erreur_liaisons": 0, - "succes": 0, - } - - for idx, entete in enumerate(entetes): - numero = _safe_strip(entete.DO_Piece) - logger.info( - f"[SQL LIST] [{idx+1}/{len(entetes)}] Traitement {numero}..." - ) - - try: - prefixes_vente = { - 0: ["DE"], - 10: ["BC"], - 30: ["BL"], - 50: ["AV", "AR"], - 60: ["FA", "FC"], - } - - prefixes_acceptes = prefixes_vente.get(type_doc, []) - - if prefixes_acceptes: - est_vente = any( - numero.upper().startswith(p) for p in prefixes_acceptes - ) - if not est_vente: - logger.info( - f"[SQL LIST] {numero} : exclu (préfixe achat)" - ) - stats["exclus_prefixe"] += 1 - continue - - logger.debug(f"[SQL LIST] {numero} : préfixe OK") - - try: - type_doc_depuis_sql = self._convertir_type_depuis_sql( - int(entete.DO_Type) - ) - - doc = { - "numero": numero, - "type": type_doc_depuis_sql, - "reference": _safe_strip(entete.DO_Ref), - "date": str(entete.DO_Date) if entete.DO_Date else "", - "date_livraison": ( - str(entete.DO_DateLivr) - if entete.DO_DateLivr - else "" - ), - "date_expedition": ( - str(entete.DO_DateExpedition) - if entete.DO_DateExpedition - else "" - ), - "client_code": _safe_strip(entete.DO_Tiers), - "client_intitule": _safe_strip(entete.CT_Intitule), - "client_adresse": _safe_strip(entete.CT_Adresse), - "client_code_postal": _safe_strip( - entete.CT_CodePostal - ), - "client_ville": _safe_strip(entete.CT_Ville), - "client_telephone": _safe_strip( - entete.CT_Telephone - ), - "client_email": _safe_strip(entete.CT_EMail), - "contact": _safe_strip(entete.DO_Contact), - "total_ht": ( - float(entete.DO_TotalHT) - if entete.DO_TotalHT - else 0.0 - ), - "total_ht_net": ( - float(entete.DO_TotalHTNet) - if entete.DO_TotalHTNet - else 0.0 - ), - "total_ttc": ( - float(entete.DO_TotalTTC) - if entete.DO_TotalTTC - else 0.0 - ), - "net_a_payer": ( - float(entete.DO_NetAPayer) - if entete.DO_NetAPayer - else 0.0 - ), - "montant_regle": ( - float(entete.DO_MontantRegle) - if entete.DO_MontantRegle - else 0.0 - ), - "reliquat": ( - float(entete.DO_Reliquat) - if entete.DO_Reliquat - else 0.0 - ), - "taux_escompte": ( - float(entete.DO_TxEscompte) - if entete.DO_TxEscompte - else 0.0 - ), - "escompte": ( - float(entete.DO_Escompte) - if entete.DO_Escompte - else 0.0 - ), - "taxe1": ( - float(entete.DO_Taxe1) if entete.DO_Taxe1 else 0.0 - ), - "taxe2": ( - float(entete.DO_Taxe2) if entete.DO_Taxe2 else 0.0 - ), - "taxe3": ( - float(entete.DO_Taxe3) if entete.DO_Taxe3 else 0.0 - ), - "code_taxe1": _safe_strip(entete.DO_CodeTaxe1), - "code_taxe2": _safe_strip(entete.DO_CodeTaxe2), - "code_taxe3": _safe_strip(entete.DO_CodeTaxe3), - "statut": ( - int(entete.DO_Statut) - if entete.DO_Statut is not None - else 0 - ), - "statut_estatut": ( - int(entete.DO_EStatut) - if entete.DO_EStatut is not None - else 0 - ), - "imprime": ( - int(entete.DO_Imprim) - if entete.DO_Imprim is not None - else 0 - ), - "valide": ( - int(entete.DO_Valide) - if entete.DO_Valide is not None - else 0 - ), - "cloture": ( - int(entete.DO_Cloture) - if entete.DO_Cloture is not None - else 0 - ), - "transfere": ( - int(entete.DO_Transfere) - if entete.DO_Transfere is not None - else 0 - ), - "souche": _safe_strip(entete.DO_Souche), - "piece_origine": _safe_strip(entete.DO_PieceOrig), - "guid": _safe_strip(entete.DO_GUID), - "ca_num": _safe_strip(entete.CA_Num), - "cg_num": _safe_strip(entete.CG_Num), - "expedition": _safe_strip(entete.DO_Expedit), - "condition": _safe_strip(entete.DO_Condition), - "tarif": _safe_strip(entete.DO_Tarif), - "type_frais": ( - int(entete.DO_TypeFrais) - if entete.DO_TypeFrais is not None - else 0 - ), - "valeur_frais": ( - float(entete.DO_ValFrais) - if entete.DO_ValFrais - else 0.0 - ), - "type_franco": ( - int(entete.DO_TypeFranco) - if entete.DO_TypeFranco is not None - else 0 - ), - "valeur_franco": ( - float(entete.DO_ValFranco) - if entete.DO_ValFranco - else 0.0 - ), - "lignes": [], - } - - logger.debug( - f"[SQL LIST] {numero} : document de base créé" - ) - - except Exception as e: - logger.error( - f"[SQL LIST] {numero} : ERREUR construction base: {e}", - exc_info=True, - ) - stats["erreur_construction"] += 1 - continue - - try: - cursor.execute( - """ - SELECT dl.*, - a.AR_Design, a.FA_CodeFamille, a.AR_PrixTTC, a.AR_PrixVen, a.AR_PrixAch, - a.AR_Gamme1, a.AR_Gamme2, a.AR_CodeBarre, a.AR_CoutStd, - a.AR_PoidsNet, a.AR_PoidsBrut, a.AR_UniteVen, - a.AR_Type, a.AR_Nature, a.AR_Escompte, a.AR_Garantie - FROM F_DOCLIGNE dl - LEFT JOIN F_ARTICLE a ON dl.AR_Ref = a.AR_Ref - WHERE dl.DO_Piece = ? AND dl.DO_Type = ? - ORDER BY dl.DL_Ligne - """, - (numero, type_doc_sql), - ) - - for ligne_row in cursor.fetchall(): - montant_ht = ( - float(ligne_row.DL_MontantHT) - if ligne_row.DL_MontantHT - else 0.0 - ) - montant_net = ( - float(ligne_row.DL_MontantNet) - if hasattr(ligne_row, "DL_MontantNet") - and ligne_row.DL_MontantNet - else montant_ht - ) - - taux_taxe1 = ( - float(ligne_row.DL_Taxe1) - if hasattr(ligne_row, "DL_Taxe1") - and ligne_row.DL_Taxe1 - else 0.0 - ) - taux_taxe2 = ( - float(ligne_row.DL_Taxe2) - if hasattr(ligne_row, "DL_Taxe2") - and ligne_row.DL_Taxe2 - else 0.0 - ) - taux_taxe3 = ( - float(ligne_row.DL_Taxe3) - if hasattr(ligne_row, "DL_Taxe3") - and ligne_row.DL_Taxe3 - else 0.0 - ) - - total_taux_taxes = taux_taxe1 + taux_taxe2 + taux_taxe3 - montant_ttc = montant_net * (1 + total_taux_taxes / 100) - - montant_taxe1 = montant_net * (taux_taxe1 / 100) - montant_taxe2 = montant_net * (taux_taxe2 / 100) - montant_taxe3 = montant_net * (taux_taxe3 / 100) - - ligne = { - "numero_ligne": ( - int(ligne_row.DL_Ligne) - if ligne_row.DL_Ligne - else 0 - ), - "article_code": _safe_strip(ligne_row.AR_Ref), - "designation": _safe_strip( - ligne_row.DL_Design - ), - "designation_article": _safe_strip( - ligne_row.AR_Design - ), - "quantite": ( - float(ligne_row.DL_Qte) - if ligne_row.DL_Qte - else 0.0 - ), - "quantite_livree": ( - float(ligne_row.DL_QteLiv) - if hasattr(ligne_row, "DL_QteLiv") - and ligne_row.DL_QteLiv - else 0.0 - ), - "quantite_reservee": ( - float(ligne_row.DL_QteRes) - if hasattr(ligne_row, "DL_QteRes") - and ligne_row.DL_QteRes - else 0.0 - ), - "unite": ( - _safe_strip(ligne_row.DL_Unite) - if hasattr(ligne_row, "DL_Unite") - else "" - ), - "prix_unitaire_ht": ( - float(ligne_row.DL_PrixUnitaire) - if ligne_row.DL_PrixUnitaire - else 0.0 - ), - "prix_unitaire_achat": ( - float(ligne_row.AR_PrixAch) - if ligne_row.AR_PrixAch - else 0.0 - ), - "prix_unitaire_vente": ( - float(ligne_row.AR_PrixVen) - if ligne_row.AR_PrixVen - else 0.0 - ), - "prix_unitaire_ttc": ( - float(ligne_row.AR_PrixTTC) - if ligne_row.AR_PrixTTC - else 0.0 - ), - "montant_ligne_ht": montant_ht, - "montant_ligne_net": montant_net, - "montant_ligne_ttc": montant_ttc, - "remise_valeur1": ( - float(ligne_row.DL_Remise01REM_Valeur) - if hasattr(ligne_row, "DL_Remise01REM_Valeur") - and ligne_row.DL_Remise01REM_Valeur - else 0.0 - ), - "remise_type1": ( - int(ligne_row.DL_Remise01REM_Type) - if hasattr(ligne_row, "DL_Remise01REM_Type") - and ligne_row.DL_Remise01REM_Type - else 0 - ), - "remise_valeur2": ( - float(ligne_row.DL_Remise02REM_Valeur) - if hasattr(ligne_row, "DL_Remise02REM_Valeur") - and ligne_row.DL_Remise02REM_Valeur - else 0.0 - ), - "remise_type2": ( - int(ligne_row.DL_Remise02REM_Type) - if hasattr(ligne_row, "DL_Remise02REM_Type") - and ligne_row.DL_Remise02REM_Type - else 0 - ), - "remise_article": ( - float(ligne_row.AR_Escompte) - if ligne_row.AR_Escompte - else 0.0 - ), - "taux_taxe1": taux_taxe1, - "montant_taxe1": montant_taxe1, - "taux_taxe2": taux_taxe2, - "montant_taxe2": montant_taxe2, - "taux_taxe3": taux_taxe3, - "montant_taxe3": montant_taxe3, - "total_taxes": montant_taxe1 - + montant_taxe2 - + montant_taxe3, - "famille_article": _safe_strip( - ligne_row.FA_CodeFamille - ), - "gamme1": _safe_strip(ligne_row.AR_Gamme1), - "gamme2": _safe_strip(ligne_row.AR_Gamme2), - "code_barre": _safe_strip( - ligne_row.AR_CodeBarre - ), - "type_article": _safe_strip(ligne_row.AR_Type), - "nature_article": _safe_strip( - ligne_row.AR_Nature - ), - "garantie": _safe_strip(ligne_row.AR_Garantie), - "cout_standard": ( - float(ligne_row.AR_CoutStd) - if ligne_row.AR_CoutStd - else 0.0 - ), - "poids_net": ( - float(ligne_row.AR_PoidsNet) - if ligne_row.AR_PoidsNet - else 0.0 - ), - "poids_brut": ( - float(ligne_row.AR_PoidsBrut) - if ligne_row.AR_PoidsBrut - else 0.0 - ), - "unite_vente": _safe_strip( - ligne_row.AR_UniteVen - ), - "date_livraison_ligne": ( - str(ligne_row.DL_DateLivr) - if hasattr(ligne_row, "DL_DateLivr") - and ligne_row.DL_DateLivr - else "" - ), - "statut_ligne": ( - int(ligne_row.DL_Statut) - if hasattr(ligne_row, "DL_Statut") - and ligne_row.DL_Statut is not None - else 0 - ), - "depot": ( - _safe_strip(ligne_row.DE_No) - if hasattr(ligne_row, "DE_No") - else "" - ), - "numero_commande": ( - _safe_strip(ligne_row.DL_NoColis) - if hasattr(ligne_row, "DL_NoColis") - else "" - ), - "num_colis": ( - _safe_strip(ligne_row.DL_Colis) - if hasattr(ligne_row, "DL_Colis") - else "" - ), - } - doc["lignes"].append(ligne) - - doc["nb_lignes"] = len(doc["lignes"]) - doc["total_ht_calcule"] = sum( - l.get("montant_ligne_ht", 0) for l in doc["lignes"] - ) - doc["total_ttc_calcule"] = sum( - l.get("montant_ligne_ttc", 0) for l in doc["lignes"] - ) - doc["total_taxes_calcule"] = sum( - l.get("total_taxes", 0) for l in doc["lignes"] - ) - - logger.debug( - f"[SQL LIST] {numero} : {doc['nb_lignes']} lignes chargées" - ) - - except Exception as e: - logger.error( - f"[SQL LIST] {numero} : ERREUR lignes: {e}", - exc_info=True, - ) - stats["erreur_lignes"] += 1 - - documents.append(doc) - stats["succes"] += 1 - logger.info( - f"[SQL LIST] {numero} : AJOUTÉ à la liste (total: {len(documents)})" - ) - - except Exception as e: - logger.error( - f"[SQL LIST] {numero} : EXCEPTION GLOBALE - DOCUMENT EXCLU: {e}", - exc_info=True, - ) - continue - - logger.info(f"[SQL LIST] ═══════════════════════════") - logger.info(f"[SQL LIST] STATISTIQUES FINALES:") - logger.info(f"[SQL LIST] Total SQL: {stats['total']}") - logger.info(f"[SQL LIST] Exclus préfixe: {stats['exclus_prefixe']}") - logger.info( - f"[SQL LIST] Erreur construction: {stats['erreur_construction']}" - ) - logger.info(f"[SQL LIST] Erreur lignes: {stats['erreur_lignes']}") - logger.info( - f"[SQL LIST] Erreur transformations: {stats['erreur_transformations']}" - ) - logger.info(f"[SQL LIST] Erreur liaisons: {stats['erreur_liaisons']}") - logger.info(f"[SQL LIST] SUCCÈS: {stats['succes']}") - logger.info(f"[SQL LIST] Documents retournés: {len(documents)}") - logger.info(f"[SQL LIST] ═══════════════════════════") - - return documents - - except Exception as e: - logger.error(f" Erreur GLOBALE listage: {e}", exc_info=True) - return [] - + logger.error(f"Erreur obtention contact: {e}") + raise RuntimeError(f"Erreur lecture contact: {str(e)}") + def lister_tous_devis_cache(self, filtre=""): - return self._lister_documents_avec_lignes_sql(type_doc=0, filtre=filtre) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lister_documents_avec_lignes_sql(cursor, type_doc=0, filtre=filtre) def lire_devis_cache(self, numero): - return self._lire_document_sql(numero, type_doc=0) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc=0) def lister_toutes_commandes_cache(self, filtre=""): - return self._lister_documents_avec_lignes_sql(type_doc=1, filtre=filtre) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lister_documents_avec_lignes_sql(cursor, type_doc=1, filtre=filtre) def lire_commande_cache(self, numero): - return self._lire_document_sql(numero, type_doc=1) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc=1) def lister_toutes_factures_cache(self, filtre=""): - return self._lister_documents_avec_lignes_sql(type_doc=6, filtre=filtre) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lister_documents_avec_lignes_sql(cursor, type_doc=6, filtre=filtre) def lire_facture_cache(self, numero): - return self._lire_document_sql(numero, type_doc=6) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc=6) def lister_tous_fournisseurs_cache(self, filtre=""): return self.lister_tous_fournisseurs() @@ -2374,405 +1557,24 @@ class SageConnector: return self.lire_fournisseur() def lister_toutes_livraisons_cache(self, filtre=""): - return self._lister_documents_avec_lignes_sql(type_doc=3, filtre=filtre) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lister_documents_avec_lignes_sql(cursor, type_doc=3, filtre=filtre) def lire_livraison_cache(self, numero): - return self._lire_document_sql(numero, type_doc=3) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc=3) def lister_tous_avoirs_cache(self, filtre=""): - return self._lister_documents_avec_lignes_sql(type_doc=5, filtre=filtre) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lister_documents_avec_lignes_sql(cursor, type_doc=5, filtre=filtre) def lire_avoir_cache(self, numero): - return self._lire_document_sql(numero, type_doc=5) - - def _extraire_fournisseur_enrichi(self, fourn_obj): - try: - numero = getattr(fourn_obj, "CT_Num", "").strip() - if not numero: - return None - - intitule = getattr(fourn_obj, "CT_Intitule", "").strip() - - data = { - "numero": numero, - "intitule": intitule, - "type": 1, # Fournisseur - "est_fournisseur": True, - } - - try: - sommeil = getattr(fourn_obj, "CT_Sommeil", 0) - data["est_actif"] = sommeil == 0 - data["en_sommeil"] = sommeil == 1 - except: - data["est_actif"] = True - data["en_sommeil"] = False - - try: - adresse_obj = getattr(fourn_obj, "Adresse", None) - if adresse_obj: - data["adresse"] = getattr(adresse_obj, "Adresse", "").strip() - data["complement"] = getattr(adresse_obj, "Complement", "").strip() - data["code_postal"] = getattr(adresse_obj, "CodePostal", "").strip() - data["ville"] = getattr(adresse_obj, "Ville", "").strip() - data["region"] = getattr(adresse_obj, "Region", "").strip() - data["pays"] = getattr(adresse_obj, "Pays", "").strip() - - parties_adresse = [] - if data["adresse"]: - parties_adresse.append(data["adresse"]) - if data["complement"]: - parties_adresse.append(data["complement"]) - if data["code_postal"] or data["ville"]: - ville_cp = f"{data['code_postal']} {data['ville']}".strip() - if ville_cp: - parties_adresse.append(ville_cp) - if data["pays"]: - parties_adresse.append(data["pays"]) - - data["adresse_complete"] = ", ".join(parties_adresse) - else: - data["adresse"] = "" - data["complement"] = "" - data["code_postal"] = "" - data["ville"] = "" - data["region"] = "" - data["pays"] = "" - data["adresse_complete"] = "" - except Exception as e: - logger.debug(f"Erreur adresse fournisseur {numero}: {e}") - data["adresse"] = "" - data["complement"] = "" - data["code_postal"] = "" - data["ville"] = "" - data["region"] = "" - data["pays"] = "" - data["adresse_complete"] = "" - - try: - telecom_obj = getattr(fourn_obj, "Telecom", None) - if telecom_obj: - data["telephone"] = getattr(telecom_obj, "Telephone", "").strip() - data["portable"] = getattr(telecom_obj, "Portable", "").strip() - data["telecopie"] = getattr(telecom_obj, "Telecopie", "").strip() - data["email"] = getattr(telecom_obj, "EMail", "").strip() - - try: - site = ( - getattr(telecom_obj, "Site", None) - or getattr(telecom_obj, "Web", None) - or getattr(telecom_obj, "SiteWeb", "") - ) - data["site_web"] = str(site).strip() if site else "" - except: - data["site_web"] = "" - else: - data["telephone"] = "" - data["portable"] = "" - data["telecopie"] = "" - data["email"] = "" - data["site_web"] = "" - except Exception as e: - logger.debug(f"Erreur telecom fournisseur {numero}: {e}") - data["telephone"] = "" - data["portable"] = "" - data["telecopie"] = "" - data["email"] = "" - data["site_web"] = "" - - try: - data["siret"] = getattr(fourn_obj, "CT_Siret", "").strip() - except: - data["siret"] = "" - - try: - if data["siret"] and len(data["siret"]) >= 9: - data["siren"] = data["siret"][:9] - else: - data["siren"] = getattr(fourn_obj, "CT_Siren", "").strip() - except: - data["siren"] = "" - - try: - data["tva_intra"] = getattr(fourn_obj, "CT_Identifiant", "").strip() - except: - data["tva_intra"] = "" - - try: - data["code_naf"] = ( - getattr(fourn_obj, "CT_CodeNAF", "").strip() - or getattr(fourn_obj, "CT_APE", "").strip() - ) - except: - data["code_naf"] = "" - - try: - data["forme_juridique"] = getattr( - fourn_obj, "CT_FormeJuridique", "" - ).strip() - except: - data["forme_juridique"] = "" - - try: - cat_tarif = getattr(fourn_obj, "N_CatTarif", None) - data["categorie_tarifaire"] = ( - int(cat_tarif) if cat_tarif is not None else None - ) - except: - data["categorie_tarifaire"] = None - - try: - cat_compta = getattr(fourn_obj, "N_CatCompta", None) - data["categorie_comptable"] = ( - int(cat_compta) if cat_compta is not None else None - ) - except: - data["categorie_comptable"] = None - - try: - cond_regl = getattr(fourn_obj, "CT_CondRegl", "").strip() - data["conditions_reglement_code"] = cond_regl - - if cond_regl: - try: - cond_obj = getattr(fourn_obj, "ConditionReglement", None) - if cond_obj: - cond_obj.Read() - data["conditions_reglement_libelle"] = getattr( - cond_obj, "C_Intitule", "" - ).strip() - else: - data["conditions_reglement_libelle"] = "" - except: - data["conditions_reglement_libelle"] = "" - else: - data["conditions_reglement_libelle"] = "" - except: - data["conditions_reglement_code"] = "" - data["conditions_reglement_libelle"] = "" - - try: - mode_regl = getattr(fourn_obj, "CT_ModeRegl", "").strip() - data["mode_reglement_code"] = mode_regl - - if mode_regl: - try: - mode_obj = getattr(fourn_obj, "ModeReglement", None) - if mode_obj: - mode_obj.Read() - data["mode_reglement_libelle"] = getattr( - mode_obj, "M_Intitule", "" - ).strip() - else: - data["mode_reglement_libelle"] = "" - except: - data["mode_reglement_libelle"] = "" - else: - data["mode_reglement_libelle"] = "" - except: - data["mode_reglement_code"] = "" - data["mode_reglement_libelle"] = "" - - data["coordonnees_bancaires"] = [] - - try: - factory_banque = getattr(fourn_obj, "FactoryBanque", None) - - if factory_banque: - index = 1 - while index <= 5: # Max 5 comptes bancaires - try: - banque_persist = factory_banque.List(index) - if banque_persist is None: - break - - banque = win32com.client.CastTo( - banque_persist, "IBOBanque3" - ) - banque.Read() - - compte_bancaire = { - "banque_nom": getattr( - banque, "BI_Intitule", "" - ).strip(), - "iban": getattr(banque, "RIB_Iban", "").strip(), - "bic": getattr(banque, "RIB_Bic", "").strip(), - "code_banque": getattr( - banque, "RIB_Banque", "" - ).strip(), - "code_guichet": getattr( - banque, "RIB_Guichet", "" - ).strip(), - "numero_compte": getattr( - banque, "RIB_Compte", "" - ).strip(), - "cle_rib": getattr(banque, "RIB_Cle", "").strip(), - } - - if ( - compte_bancaire["iban"] - or compte_bancaire["numero_compte"] - ): - data["coordonnees_bancaires"].append(compte_bancaire) - - index += 1 - except: - break - except Exception as e: - logger.debug( - f"Erreur coordonnées bancaires fournisseur {numero}: {e}" - ) - - if data["coordonnees_bancaires"]: - data["iban_principal"] = data["coordonnees_bancaires"][0].get( - "iban", "" - ) - data["bic_principal"] = data["coordonnees_bancaires"][0].get("bic", "") - else: - data["iban_principal"] = "" - data["bic_principal"] = "" - - data["contacts"] = [] - - try: - factory_contact = getattr(fourn_obj, "FactoryContact", None) - - if factory_contact: - index = 1 - while index <= 20: # Max 20 contacts - try: - contact_persist = factory_contact.List(index) - if contact_persist is None: - break - - contact = win32com.client.CastTo( - contact_persist, "IBOContact3" - ) - contact.Read() - - contact_data = { - "nom": getattr(contact, "CO_Nom", "").strip(), - "prenom": getattr(contact, "CO_Prenom", "").strip(), - "fonction": getattr(contact, "CO_Fonction", "").strip(), - "service": getattr(contact, "CO_Service", "").strip(), - "telephone": getattr( - contact, "CO_Telephone", "" - ).strip(), - "portable": getattr(contact, "CO_Portable", "").strip(), - "email": getattr(contact, "CO_EMail", "").strip(), - } - - nom_complet = f"{contact_data['prenom']} {contact_data['nom']}".strip() - if nom_complet: - contact_data["nom_complet"] = nom_complet - else: - contact_data["nom_complet"] = contact_data["nom"] - - if contact_data["nom"]: - data["contacts"].append(contact_data) - - index += 1 - except: - break - except Exception as e: - logger.debug(f"Erreur contacts fournisseur {numero}: {e}") - - data["nb_contacts"] = len(data["contacts"]) - - if data["contacts"]: - data["contact_principal"] = data["contacts"][0] - else: - data["contact_principal"] = None - - try: - data["encours_autorise"] = float(getattr(fourn_obj, "CT_Encours", 0.0)) - except: - data["encours_autorise"] = 0.0 - - try: - data["ca_annuel"] = float(getattr(fourn_obj, "CT_ChiffreAffaire", 0.0)) - except: - data["ca_annuel"] = 0.0 - - try: - data["compte_general"] = getattr(fourn_obj, "CG_Num", "").strip() - except: - data["compte_general"] = "" - - try: - date_creation = getattr(fourn_obj, "CT_DateCreate", None) - data["date_creation"] = str(date_creation) if date_creation else "" - except: - data["date_creation"] = "" - - try: - date_modif = getattr(fourn_obj, "CT_DateModif", None) - data["date_modification"] = str(date_modif) if date_modif else "" - except: - data["date_modification"] = "" - - return data - - except Exception as e: - logger.error(f" Erreur extraction fournisseur: {e}", exc_info=True) - return { - "numero": getattr(fourn_obj, "CT_Num", "").strip(), - "intitule": getattr(fourn_obj, "CT_Intitule", "").strip(), - "type": 1, - "est_fournisseur": True, - "est_actif": True, - "en_sommeil": False, - "adresse": "", - "complement": "", - "code_postal": "", - "ville": "", - "region": "", - "pays": "", - "adresse_complete": "", - "telephone": "", - "portable": "", - "telecopie": "", - "email": "", - "site_web": "", - "siret": "", - "siren": "", - "tva_intra": "", - "code_naf": "", - "forme_juridique": "", - "categorie_tarifaire": None, - "categorie_comptable": None, - "conditions_reglement_code": "", - "conditions_reglement_libelle": "", - "mode_reglement_code": "", - "mode_reglement_libelle": "", - "iban_principal": "", - "bic_principal": "", - "coordonnees_bancaires": [], - "contacts": [], - "nb_contacts": 0, - "contact_principal": None, - "encours_autorise": 0.0, - "ca_annuel": 0.0, - "compte_general": "", - "date_creation": "", - "date_modification": "", - } - - def normaliser_date(self, valeur): - if isinstance(valeur, str): - try: - return datetime.fromisoformat(valeur) - except ValueError: - return datetime.now() - - elif isinstance(valeur, date): - return datetime.combine(valeur, datetime.min.time()) - - elif isinstance(valeur, datetime): - return valeur - - else: - return datetime.now() + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc=5) def creer_devis_enrichi(self, devis_data: dict, forcer_brouillon: bool = False): if not self.cial: @@ -2805,12 +1607,12 @@ class SageConnector: logger.info(" Document devis créé") doc.DO_Date = pywintypes.Time( - self.normaliser_date(devis_data.get("date_devis")) + normaliser_date(devis_data.get("date_devis")) ) if "date_livraison" in devis_data and devis_data["date_livraison"]: doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(devis_data["date_livraison"]) + normaliser_date(devis_data["date_livraison"]) ) factory_client = self.cial.CptaApplication.FactoryClient @@ -2940,7 +1742,7 @@ class SageConnector: except: logger.debug("Process() ignoré pour brouillon") - numero_devis = self._recuperer_numero_devis(process, doc) + numero_devis = _recuperer_numero_devis(process, doc) if not numero_devis: raise RuntimeError(" Numéro devis vide après création") @@ -2985,7 +1787,7 @@ class SageConnector: time.sleep(0.5) - doc_final_data = self._relire_devis( + doc_final_data = _relire_devis( numero_devis, devis_data, forcer_brouillon ) @@ -3008,116 +1810,6 @@ class SageConnector: logger.error(f" ERREUR CRÉATION DEVIS: {e}", exc_info=True) raise RuntimeError(f"Échec création devis: {str(e)}") - def _recuperer_numero_devis(self, process, doc): - """Récupère le numéro du devis créé via plusieurs méthodes.""" - numero_devis = None - - try: - doc_result = process.DocumentResult - if doc_result: - doc_result = win32com.client.CastTo(doc_result, "IBODocumentVente3") - doc_result.Read() - numero_devis = getattr(doc_result, "DO_Piece", "") - except: - pass - - if not numero_devis: - numero_devis = getattr(doc, "DO_Piece", "") - - if not numero_devis: - try: - doc.SetDefaultNumPiece() - doc.Write() - doc.Read() - numero_devis = getattr(doc, "DO_Piece", "") - except: - pass - - return numero_devis - - def _relire_devis(self, numero_devis, devis_data, forcer_brouillon): - """Relit le devis créé et extrait les informations finales.""" - factory_doc = self.cial.FactoryDocumentVente - persist_reread = factory_doc.ReadPiece(0, numero_devis) - - if not persist_reread: - logger.debug("ReadPiece échoué, recherche dans List()...") - persist_reread = self._rechercher_devis_dans_liste( - numero_devis, factory_doc - ) - - if persist_reread: - doc_final = win32com.client.CastTo(persist_reread, "IBODocumentVente3") - doc_final.Read() - - total_ht = float(getattr(doc_final, "DO_TotalHT", 0.0)) - total_ttc = float(getattr(doc_final, "DO_TotalTTC", 0.0)) - statut_final = getattr(doc_final, "DO_Statut", 0) - reference_final = getattr(doc_final, "DO_Ref", "") - - date_livraison_final = None - - try: - date_livr = getattr(doc_final, "DO_DateLivr", None) - if date_livr: - date_livraison_final = date_livr.strftime("%Y-%m-%d") - except: - pass - else: - total_calcule = sum( - l.get("montant_ligne_ht", 0) for l in devis_data["lignes"] - ) - total_ht = total_calcule - total_ttc = round(total_calcule * 1.20, 2) - statut_final = 0 if forcer_brouillon else 2 - reference_final = devis_data.get("reference", "") - date_livraison_final = devis_data.get("date_livraison") - - logger.info(f" Total HT: {total_ht}€") - logger.info(f" Total TTC: {total_ttc}€") - logger.info(f" Statut final: {statut_final}") - if reference_final: - logger.info(f" Référence: {reference_final}") - if date_livraison_final: - logger.info(f" Date livraison: {date_livraison_final}") - - return { - "numero_devis": numero_devis, - "total_ht": total_ht, - "total_ttc": total_ttc, - "nb_lignes": len(devis_data["lignes"]), - "client_code": devis_data["client"]["code"], - "date_devis": str(devis_data.get("date_devis", "")), - "date_livraison": date_livraison_final, - "reference": reference_final, - "statut": statut_final, - } - - def _rechercher_devis_dans_liste(self, numero_devis, factory_doc): - """Recherche un devis dans les 100 premiers éléments de la liste.""" - index = 1 - while index < 100: - try: - persist_test = factory_doc.List(index) - if persist_test is None: - break - - doc_test = win32com.client.CastTo(persist_test, "IBODocumentVente3") - doc_test.Read() - - if ( - getattr(doc_test, "DO_Type", -1) == 0 - and getattr(doc_test, "DO_Piece", "") == numero_devis - ): - logger.info(f" Document trouvé à l'index {index}") - return persist_test - - index += 1 - except: - index += 1 - - return None - def modifier_devis(self, numero: str, devis_data: Dict) -> Dict: logger.info("=" * 100) logger.info("=" * 100) @@ -3252,7 +1944,7 @@ class SageConnector: ancienne_date_str = ancienne_date.strftime("%Y-%m-%d") if ancienne_date else "None" logger.info(f" Actuelle: {ancienne_date_str}") - nouvelle_date = self.normaliser_date(devis_data_temp["date_devis"]) + nouvelle_date = normaliser_date(devis_data_temp["date_devis"]) nouvelle_date_str = nouvelle_date.strftime("%Y-%m-%d") logger.info(f" Cible: {nouvelle_date_str}") @@ -3273,7 +1965,7 @@ class SageConnector: logger.info(f" Actuelle: {ancienne_date_livr_str}") if devis_data_temp["date_livraison"]: - nouvelle_date_livr = self.normaliser_date(devis_data_temp["date_livraison"]) + nouvelle_date_livr = normaliser_date(devis_data_temp["date_livraison"]) nouvelle_date_livr_str = nouvelle_date_livr.strftime("%Y-%m-%d") logger.info(f" Cible: {nouvelle_date_livr_str}") @@ -3350,7 +2042,7 @@ class SageConnector: if modif_date: logger.info(" Modification date devis (avant lignes)...") try: - nouvelle_date = self.normaliser_date(devis_data_temp["date_devis"]) + nouvelle_date = normaliser_date(devis_data_temp["date_devis"]) doc.DO_Date = pywintypes.Time(nouvelle_date) champs_modifies.append("date_devis") logger.info(f" Date: {nouvelle_date.strftime('%Y-%m-%d')}") @@ -3361,7 +2053,7 @@ class SageConnector: logger.info(" Modification date livraison (avant lignes)...") try: if devis_data_temp["date_livraison"]: - nouvelle_date_livr = self.normaliser_date(devis_data_temp["date_livraison"]) + nouvelle_date_livr = normaliser_date(devis_data_temp["date_livraison"]) doc.DO_DateLivr = pywintypes.Time(nouvelle_date_livr) logger.info(f" Date livraison: {nouvelle_date_livr.strftime('%Y-%m-%d')}") else: @@ -3618,19 +2310,23 @@ class SageConnector: def lire_devis(self, numero_devis): try: - devis = self._lire_document_sql(numero_devis, type_doc=0) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + devis = _lire_document_sql(cursor, numero_devis, type_doc=0) - if not devis: - return None + if not devis: + return None - return devis + return devis except Exception as e: logger.error(f" Erreur SQL lecture devis {numero_devis}: {e}") return None def lire_document(self, numero, type_doc): - return self._lire_document_sql(numero, type_doc) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc) def transformer_document( self, @@ -4100,62 +2796,16 @@ class SageConnector: logger.error(f" Erreur SQL prospect {code_prospect}: {e}") return None - def lister_avoirs(self, limit=100, statut=None): - try: - with self._get_sql_connection() as conn: - cursor = conn.cursor() - - query = f""" - SELECT TOP ({limit}) - d.DO_Piece, d.DO_Date, d.DO_Ref, d.DO_TotalHT, d.DO_TotalTTC, - d.DO_Statut, d.CT_Num, c.CT_Intitule - FROM F_DOCENTETE d - LEFT JOIN F_COMPTET c ON d.CT_Num = c.CT_Num - WHERE d.DO_Type = 50 - """ - - params = [] - - if statut is not None: - query += " AND d.DO_Statut = ?" - params.append(statut) - - query += " ORDER BY d.DO_Date DESC" - - cursor.execute(query, params) - rows = cursor.fetchall() - - avoirs = [] - for row in rows: - avoirs.append( - { - "numero": _safe_strip(row.DO_Piece), - "reference": _safe_strip(row.DO_Ref), - "date": str(row.DO_Date) if row.DO_Date else "", - "client_code": _safe_strip(row.CT_Num), - "client_intitule": _safe_strip(row.CT_Intitule), - "total_ht": ( - float(row.DO_TotalHT) if row.DO_TotalHT else 0.0 - ), - "total_ttc": ( - float(row.DO_TotalTTC) if row.DO_TotalTTC else 0.0 - ), - "statut": row.DO_Statut if row.DO_Statut is not None else 0, - } - ) - - return avoirs - - except Exception as e: - logger.error(f" Erreur SQL avoirs: {e}") - return [] - def lire_avoir(self, numero): - return self._lire_document_sql(numero, type_doc=50) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc=50) def lire_livraison(self, numero): """ Lit UNE livraison via SQL (avec lignes)""" - return self._lire_document_sql(numero, type_doc=30) + with self._get_sql_connection() as conn: + cursor = conn.cursor() + return _lire_document_sql(cursor, numero, type_doc=30) def creer_contact(self, contact_data: Dict) -> Dict: if not self.cial: @@ -4762,7 +3412,7 @@ class SageConnector: def lister_contacts(self, numero: str) -> List[Dict]: try: with self._get_sql_connection() as conn: - return self._get_contacts_client(numero, conn) + return _get_contacts_client(numero, conn) except Exception as e: logger.error(f"Erreur liste contacts: {e}") raise RuntimeError(f"Erreur lecture contacts: {str(e)}") @@ -5890,12 +4540,12 @@ class SageConnector: logger.info(" Document commande créé") doc.DO_Date = pywintypes.Time( - self.normaliser_date(commande_data.get("date_commande")) + normaliser_date(commande_data.get("date_commande")) ) if ("date_livraison" in commande_data and commande_data["date_livraison"]): doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(commande_data["date_livraison"]) + normaliser_date(commande_data["date_livraison"]) ) logger.info( f" Date livraison: {commande_data['date_livraison']}" @@ -6113,7 +4763,7 @@ class SageConnector: "nb_lignes": len(commande_data["lignes"]), "client_code": commande_data["client"]["code"], "date_commande": str( - self.normaliser_date(commande_data.get("date_commande")) + normaliser_date(commande_data.get("date_commande")) ), "date_livraison": date_livraison_final, "reference": reference_finale, @@ -6274,7 +4924,7 @@ class SageConnector: if modif_date: logger.info(" Modification date commande...") doc.DO_Date = pywintypes.Time( - self.normaliser_date( + normaliser_date( commande_data_temp.get("date_commande") ) ) @@ -6283,7 +4933,7 @@ class SageConnector: if modif_date_livraison: logger.info(" Modification date livraison...") doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(commande_data_temp["date_livraison"]) + normaliser_date(commande_data_temp["date_livraison"]) ) logger.info( f" Date livraison: {commande_data_temp['date_livraison']}" @@ -6343,7 +4993,7 @@ class SageConnector: if modif_date: doc.DO_Date = pywintypes.Time( - self.normaliser_date( + normaliser_date( commande_data_temp.get("date_commande") ) ) @@ -6352,7 +5002,7 @@ class SageConnector: if modif_date_livraison: doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(commande_data_temp["date_livraison"]) + normaliser_date(commande_data_temp["date_livraison"]) ) logger.info(" Date livraison modifiée") champs_modifies.append("date_livraison") @@ -6621,7 +5271,7 @@ class SageConnector: logger.info(" Document livraison créé") doc.DO_Date = pywintypes.Time( - self.normaliser_date(livraison_data.get("date_livraison")) + normaliser_date(livraison_data.get("date_livraison")) ) if ( @@ -6629,7 +5279,7 @@ class SageConnector: and livraison_data["date_livraison_prevue"] ): doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date( + normaliser_date( livraison_data["date_livraison_prevue"] ) ) @@ -6843,7 +5493,7 @@ class SageConnector: "nb_lignes": len(livraison_data["lignes"]), "client_code": livraison_data["client"]["code"], "date_livraison": str( - self.normaliser_date(livraison_data.get("date_livraison")) + normaliser_date(livraison_data.get("date_livraison")) ), "date_livraison_prevue": date_livraison_prevue_final, "reference": reference_finale, @@ -6967,7 +5617,7 @@ class SageConnector: if modif_date: logger.info(" Modification date livraison...") doc.DO_Date = pywintypes.Time( - self.normaliser_date( + normaliser_date( livraison_data_temp.get("date_livraison") ) ) @@ -6976,7 +5626,7 @@ class SageConnector: if modif_date_livraison_prevue: logger.info(" Modification date livraison prévue...") doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date( + normaliser_date( livraison_data_temp["date_livraison_prevue"] ) ) @@ -7012,7 +5662,7 @@ class SageConnector: if modif_date: doc.DO_Date = pywintypes.Time( - self.normaliser_date( + normaliser_date( livraison_data_temp.get("date_livraison") ) ) @@ -7021,7 +5671,7 @@ class SageConnector: if modif_date_livraison_prevue: doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date( + normaliser_date( livraison_data_temp["date_livraison_prevue"] ) ) @@ -7281,12 +5931,12 @@ class SageConnector: logger.info(" Document avoir créé") doc.DO_Date = pywintypes.Time( - self.normaliser_date(avoir_data.get("date_avoir")) + normaliser_date(avoir_data.get("date_avoir")) ) if "date_livraison" in avoir_data and avoir_data["date_livraison"]: doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(avoir_data["date_livraison"]) + normaliser_date(avoir_data["date_livraison"]) ) logger.info( f" Date livraison: {avoir_data['date_livraison']}" @@ -7491,7 +6141,7 @@ class SageConnector: "nb_lignes": len(avoir_data["lignes"]), "client_code": avoir_data["client"]["code"], "date_avoir": str( - self.normaliser_date(avoir_data.get("date_avoir")) + normaliser_date(avoir_data.get("date_avoir")) ), "date_livraison": date_livraison_final, "reference": reference_finale, @@ -7658,14 +6308,14 @@ class SageConnector: if modif_date: logger.info(" Modification date avoir...") doc.DO_Date = pywintypes.Time( - self.normaliser_date(avoir_data_temp.get("date_avoir")) + normaliser_date(avoir_data_temp.get("date_avoir")) ) champs_modifies.append("date") if modif_date_livraison: logger.info(" Modification date livraison...") doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(avoir_data_temp["date_livraison"]) + normaliser_date(avoir_data_temp["date_livraison"]) ) logger.info( f" Date livraison: {avoir_data_temp['date_livraison']}" @@ -7725,14 +6375,14 @@ class SageConnector: if modif_date: doc.DO_Date = pywintypes.Time( - self.normaliser_date(avoir_data_temp.get("date_avoir")) + normaliser_date(avoir_data_temp.get("date_avoir")) ) champs_modifies.append("date") logger.info(" Date avoir modifiée") if modif_date_livraison: doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(avoir_data_temp["date_livraison"]) + normaliser_date(avoir_data_temp["date_livraison"]) ) logger.info(" Date livraison modifiée") champs_modifies.append("date_livraison") @@ -8008,7 +6658,7 @@ class SageConnector: logger.info(" Document facture créé") doc.DO_Date = pywintypes.Time( - self.normaliser_date(facture_data.get("date_facture")) + normaliser_date(facture_data.get("date_facture")) ) if ( @@ -8016,7 +6666,7 @@ class SageConnector: and facture_data["date_livraison"] ): doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(facture_data["date_livraison"]) + normaliser_date(facture_data["date_livraison"]) ) logger.info( f" Date livraison: {facture_data['date_livraison']}" @@ -8273,7 +6923,7 @@ class SageConnector: "nb_lignes": len(facture_data["lignes"]), "client_code": facture_data["client"]["code"], "date_facture": str( - self.normaliser_date(facture_data.get("date_facture")) + normaliser_date(facture_data.get("date_facture")) ), "date_livraison": date_livraison_final, "reference": reference_finale, @@ -8441,14 +7091,14 @@ class SageConnector: if modif_date: logger.info(" Modification date facture...") doc.DO_Date = pywintypes.Time( - self.normaliser_date(facture_data_temp.get("date_facture")) + normaliser_date(facture_data_temp.get("date_facture")) ) champs_modifies.append("date") if modif_date_livraison: logger.info(" Modification date livraison...") doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(facture_data_temp["date_livraison"]) + normaliser_date(facture_data_temp["date_livraison"]) ) logger.info( f" Date livraison: {facture_data_temp['date_livraison']}" @@ -8508,14 +7158,14 @@ class SageConnector: if modif_date: doc.DO_Date = pywintypes.Time( - self.normaliser_date(facture_data_temp.get("date_facture")) + normaliser_date(facture_data_temp.get("date_facture")) ) champs_modifies.append("date") logger.info(" Date facture modifiée") if modif_date_livraison: doc.DO_DateLivr = pywintypes.Time( - self.normaliser_date(facture_data_temp["date_livraison"]) + normaliser_date(facture_data_temp["date_livraison"]) ) logger.info(" Date livraison modifiée") champs_modifies.append("date_livraison") @@ -11624,7 +10274,7 @@ class SageConnector: tiers_list = [] for row in rows: tiers = _row_to_tiers_dict(row) - tiers["contacts"] = self._get_contacts_client(row.CT_Num, conn) + tiers["contacts"] = _get_contacts_client(row.CT_Num, conn) tiers_list.append(tiers) logger.info(f" SQL: {len(tiers_list)} tiers retournés (type={type_tiers}, filtre={filtre})") @@ -11650,7 +10300,7 @@ class SageConnector: return None tiers = _row_to_tiers_dict(row) - tiers["contacts"] = self._get_contacts_client(row.CT_Num, conn) + tiers["contacts"] = _get_contacts_client(row.CT_Num, conn) logger.info(f" SQL: Tiers {code} lu avec succès") return tiers diff --git a/utils/documents/devis/devis_check.py b/utils/documents/devis/devis_check.py new file mode 100644 index 0000000..4bd22d1 --- /dev/null +++ b/utils/documents/devis/devis_check.py @@ -0,0 +1,120 @@ +import win32com.client +import logging + +logger = logging.getLogger(__name__) + +def _rechercher_devis_dans_liste(numero_devis, factory_doc): + """Recherche un devis dans les 100 premiers éléments de la liste.""" + index = 1 + while index < 100: + try: + persist_test = factory_doc.List(index) + if persist_test is None: + break + + doc_test = win32com.client.CastTo(persist_test, "IBODocumentVente3") + doc_test.Read() + + if ( + getattr(doc_test, "DO_Type", -1) == 0 + and getattr(doc_test, "DO_Piece", "") == numero_devis + ): + logger.info(f" Document trouvé à l'index {index}") + return persist_test + + index += 1 + except: + index += 1 + + return None + +def _recuperer_numero_devis(self, process, doc): + """Récupère le numéro du devis créé via plusieurs méthodes.""" + numero_devis = None + + try: + doc_result = process.DocumentResult + if doc_result: + doc_result = win32com.client.CastTo(doc_result, "IBODocumentVente3") + doc_result.Read() + numero_devis = getattr(doc_result, "DO_Piece", "") + except: + pass + + if not numero_devis: + numero_devis = getattr(doc, "DO_Piece", "") + + if not numero_devis: + try: + doc.SetDefaultNumPiece() + doc.Write() + doc.Read() + numero_devis = getattr(doc, "DO_Piece", "") + except: + pass + + return numero_devis + +def _relire_devis(self, numero_devis, devis_data, forcer_brouillon): + """Relit le devis créé et extrait les informations finales.""" + factory_doc = self.cial.FactoryDocumentVente + persist_reread = factory_doc.ReadPiece(0, numero_devis) + + if not persist_reread: + logger.debug("ReadPiece échoué, recherche dans List()...") + persist_reread = _rechercher_devis_dans_liste( + numero_devis, factory_doc + ) + + if persist_reread: + doc_final = win32com.client.CastTo(persist_reread, "IBODocumentVente3") + doc_final.Read() + + total_ht = float(getattr(doc_final, "DO_TotalHT", 0.0)) + total_ttc = float(getattr(doc_final, "DO_TotalTTC", 0.0)) + statut_final = getattr(doc_final, "DO_Statut", 0) + reference_final = getattr(doc_final, "DO_Ref", "") + + date_livraison_final = None + + try: + date_livr = getattr(doc_final, "DO_DateLivr", None) + if date_livr: + date_livraison_final = date_livr.strftime("%Y-%m-%d") + except: + pass + else: + total_calcule = sum( + l.get("montant_ligne_ht", 0) for l in devis_data["lignes"] + ) + total_ht = total_calcule + total_ttc = round(total_calcule * 1.20, 2) + statut_final = 0 if forcer_brouillon else 2 + reference_final = devis_data.get("reference", "") + date_livraison_final = devis_data.get("date_livraison") + + logger.info(f" Total HT: {total_ht}€") + logger.info(f" Total TTC: {total_ttc}€") + logger.info(f" Statut final: {statut_final}") + if reference_final: + logger.info(f" Référence: {reference_final}") + if date_livraison_final: + logger.info(f" Date livraison: {date_livraison_final}") + + return { + "numero_devis": numero_devis, + "total_ht": total_ht, + "total_ttc": total_ttc, + "nb_lignes": len(devis_data["lignes"]), + "client_code": devis_data["client"]["code"], + "date_devis": str(devis_data.get("date_devis", "")), + "date_livraison": date_livraison_final, + "reference": reference_final, + "statut": statut_final, + } + + +__all__ = [ + "_recuperer_numero_devis", + "_relire_devis" +] \ No newline at end of file diff --git a/utils/documents/devis/devis_data_sql.py b/utils/documents/devis/devis_data_sql.py deleted file mode 100644 index 3540061..0000000 --- a/utils/documents/devis/devis_data_sql.py +++ /dev/null @@ -1,89 +0,0 @@ -import win32com.client -from typing import Optional -import logging - -logger = logging.getLogger(__name__) - -def _afficher_etat_document(doc, titre: str): - """Affiche l'état complet d'un document.""" - logger.info("-" * 80) - logger.info(titre) - logger.info("-" * 80) - try: - logger.info(f" DO_Piece: {getattr(doc, 'DO_Piece', 'N/A')}") - logger.info(f" DO_Ref: '{getattr(doc, 'DO_Ref', 'N/A')}'") - logger.info(f" DO_Statut: {getattr(doc, 'DO_Statut', 'N/A')}") - - date_doc = getattr(doc, 'DO_Date', None) - date_str = date_doc.strftime('%Y-%m-%d') if date_doc else 'None' - logger.info(f" DO_Date: {date_str}") - - date_livr = getattr(doc, 'DO_DateLivr', None) - date_livr_str = date_livr.strftime('%Y-%m-%d') if date_livr else 'None' - logger.info(f" DO_DateLivr: {date_livr_str}") - - logger.info(f" DO_TotalHT: {getattr(doc, 'DO_TotalHT', 0)}€") - logger.info(f" DO_TotalTTC: {getattr(doc, 'DO_TotalTTC', 0)}€") - except Exception as e: - logger.error(f" Erreur affichage état: {e}") - logger.info("-" * 80) - - -def _compter_lignes_document(doc) -> int: - """Compte les lignes d'un document.""" - try: - try: - factory_lignes = doc.FactoryDocumentLigne - except: - factory_lignes = doc.FactoryDocumentVenteLigne - - count = 0 - index = 1 - while index <= 100: - try: - ligne_p = factory_lignes.List(index) - if ligne_p is None: - break - count += 1 - index += 1 - except: - break - return count - except Exception as e: - logger.warning(f" Erreur comptage lignes: {e}") - return 0 - - -def _rechercher_devis_par_numero(numero: str, factory): - """Recherche un devis par numéro dans la liste.""" - logger.info(f" Recherche de {numero} dans la liste...") - - index = 1 - while index < 10000: - try: - persist_test = factory.List(index) - if persist_test is None: - break - - doc_test = win32com.client.CastTo(persist_test, "IBODocumentVente3") - doc_test.Read() - - if ( - getattr(doc_test, "DO_Type", -1) == 0 - and getattr(doc_test, "DO_Piece", "") == numero - ): - logger.info(f" Trouvé à l'index {index}") - return persist_test - - index += 1 - except: - index += 1 - - logger.error(f" Devis {numero} non trouvé dans la liste") - return None - -__all__ = [ - "_afficher_etat_document", - "_compter_lignes_document", - "_rechercher_devis_par_numero" -] \ No newline at end of file diff --git a/utils/documents/devis_extraction.py b/utils/documents/devis/devis_extraction.py similarity index 100% rename from utils/documents/devis_extraction.py rename to utils/documents/devis/devis_extraction.py diff --git a/utils/documents/documents_data_sql.py b/utils/documents/documents_data_sql.py index e69de29..37f66f9 100644 --- a/utils/documents/documents_data_sql.py +++ b/utils/documents/documents_data_sql.py @@ -0,0 +1,913 @@ +import win32com.client +from typing import Optional +import logging + +logger = logging.getLogger(__name__) + +from utils.functions.functions import ( + _convertir_type_depuis_sql, + _convertir_type_pour_sql, + _safe_strip +) + +def _afficher_etat_document(doc, titre: str): + """Affiche l'état complet d'un document.""" + logger.info("-" * 80) + logger.info(titre) + logger.info("-" * 80) + try: + logger.info(f" DO_Piece: {getattr(doc, 'DO_Piece', 'N/A')}") + logger.info(f" DO_Ref: '{getattr(doc, 'DO_Ref', 'N/A')}'") + logger.info(f" DO_Statut: {getattr(doc, 'DO_Statut', 'N/A')}") + + date_doc = getattr(doc, 'DO_Date', None) + date_str = date_doc.strftime('%Y-%m-%d') if date_doc else 'None' + logger.info(f" DO_Date: {date_str}") + + date_livr = getattr(doc, 'DO_DateLivr', None) + date_livr_str = date_livr.strftime('%Y-%m-%d') if date_livr else 'None' + logger.info(f" DO_DateLivr: {date_livr_str}") + + logger.info(f" DO_TotalHT: {getattr(doc, 'DO_TotalHT', 0)}€") + logger.info(f" DO_TotalTTC: {getattr(doc, 'DO_TotalTTC', 0)}€") + except Exception as e: + logger.error(f" Erreur affichage état: {e}") + logger.info("-" * 80) + + +def _compter_lignes_document(doc) -> int: + """Compte les lignes d'un document.""" + try: + try: + factory_lignes = doc.FactoryDocumentLigne + except: + factory_lignes = doc.FactoryDocumentVenteLigne + + count = 0 + index = 1 + while index <= 100: + try: + ligne_p = factory_lignes.List(index) + if ligne_p is None: + break + count += 1 + index += 1 + except: + break + return count + except Exception as e: + logger.warning(f" Erreur comptage lignes: {e}") + return 0 + + +def _rechercher_devis_par_numero(numero: str, factory): + """Recherche un devis par numéro dans la liste.""" + logger.info(f" Recherche de {numero} dans la liste...") + + index = 1 + while index < 10000: + try: + persist_test = factory.List(index) + if persist_test is None: + break + + doc_test = win32com.client.CastTo(persist_test, "IBODocumentVente3") + doc_test.Read() + + if ( + getattr(doc_test, "DO_Type", -1) == 0 + and getattr(doc_test, "DO_Piece", "") == numero + ): + logger.info(f" Trouvé à l'index {index}") + return persist_test + + index += 1 + except: + index += 1 + + logger.error(f" Devis {numero} non trouvé dans la liste") + return None + + +def _lire_document_sql(cursor, numero: str, type_doc: int): + try: + + query = """ + SELECT + d.DO_Piece, d.DO_Date, d.DO_Ref, d.DO_TotalHT, d.DO_TotalTTC, + d.DO_Statut, d.DO_Tiers, d.DO_DateLivr, d.DO_DateExpedition, + d.DO_Contact, d.DO_TotalHTNet, d.DO_NetAPayer, + d.DO_MontantRegle, d.DO_Reliquat, d.DO_TxEscompte, d.DO_Escompte, + d.DO_Taxe1, d.DO_Taxe2, d.DO_Taxe3, + d.DO_CodeTaxe1, d.DO_CodeTaxe2, d.DO_CodeTaxe3, + d.DO_EStatut, d.DO_Imprim, d.DO_Valide, d.DO_Cloture, + d.DO_Transfere, d.DO_Souche, d.DO_PieceOrig, d.DO_GUID, + d.CA_Num, d.CG_Num, d.DO_Expedit, d.DO_Condition, + d.DO_Tarif, d.DO_TypeFrais, d.DO_ValFrais, + d.DO_TypeFranco, d.DO_ValFranco, + c.CT_Intitule, c.CT_Adresse, c.CT_CodePostal, + c.CT_Ville, c.CT_Telephone, c.CT_EMail + FROM F_DOCENTETE d + LEFT JOIN F_COMPTET c ON d.DO_Tiers = c.CT_Num + WHERE d.DO_Piece = ? AND d.DO_Type = ? + """ + + logger.info(f"[SQL READ] Lecture directe de {numero} (type={type_doc})") + + cursor.execute(query, (numero, type_doc)) + row = cursor.fetchone() + + if not row: + logger.warning( + f"[SQL READ] Document {numero} (type={type_doc}) INTROUVABLE dans F_DOCENTETE" + ) + return None + + numero_piece = _safe_strip(row[0]) + logger.info(f"[SQL READ] Document trouvé: {numero_piece}") + + + doc = { + "numero": numero_piece, + "reference": _safe_strip(row[2]), # DO_Ref + "date": str(row[1]) if row[1] else "", # DO_Date + "date_livraison": (str(row[7]) if row[7] else ""), # DO_DateLivr + "date_expedition": ( + str(row[8]) if row[8] else "" + ), # DO_DateExpedition + "client_code": _safe_strip(row[6]), # DO_Tiers + "client_intitule": _safe_strip(row[39]), # CT_Intitule + "client_adresse": _safe_strip(row[40]), # CT_Adresse + "client_code_postal": _safe_strip(row[41]), # CT_CodePostal + "client_ville": _safe_strip(row[42]), # CT_Ville + "client_telephone": _safe_strip(row[43]), # CT_Telephone + "client_email": _safe_strip(row[44]), # CT_EMail + "contact": _safe_strip(row[9]), # DO_Contact + "total_ht": float(row[3]) if row[3] else 0.0, # DO_TotalHT + "total_ht_net": float(row[10]) if row[10] else 0.0, # DO_TotalHTNet + "total_ttc": float(row[4]) if row[4] else 0.0, # DO_TotalTTC + "net_a_payer": float(row[11]) if row[11] else 0.0, # DO_NetAPayer + "montant_regle": ( + float(row[12]) if row[12] else 0.0 + ), # DO_MontantRegle + "reliquat": float(row[13]) if row[13] else 0.0, # DO_Reliquat + "taux_escompte": ( + float(row[14]) if row[14] else 0.0 + ), # DO_TxEscompte + "escompte": float(row[15]) if row[15] else 0.0, # DO_Escompte + "taxe1": float(row[16]) if row[16] else 0.0, # DO_Taxe1 + "taxe2": float(row[17]) if row[17] else 0.0, # DO_Taxe2 + "taxe3": float(row[18]) if row[18] else 0.0, # DO_Taxe3 + "code_taxe1": _safe_strip(row[19]), # DO_CodeTaxe1 + "code_taxe2": _safe_strip(row[20]), # DO_CodeTaxe2 + "code_taxe3": _safe_strip(row[21]), # DO_CodeTaxe3 + "statut": int(row[5]) if row[5] is not None else 0, # DO_Statut + "statut_estatut": ( + int(row[22]) if row[22] is not None else 0 + ), # DO_EStatut + "imprime": int(row[23]) if row[23] is not None else 0, # DO_Imprim + "valide": int(row[24]) if row[24] is not None else 0, # DO_Valide + "cloture": int(row[25]) if row[25] is not None else 0, # DO_Cloture + "transfere": ( + int(row[26]) if row[26] is not None else 0 + ), # DO_Transfere + "souche": int(row[27]) if row[27] is not None else 0, # DO_Souche + "piece_origine": _safe_strip(row[28]), # DO_PieceOrig + "guid": _safe_strip(row[29]), # DO_GUID + "ca_num": _safe_strip(row[30]), # CA_Num + "cg_num": _safe_strip(row[31]), # CG_Num + "expedition": ( + int(row[32]) if row[32] is not None else 1 + ), # DO_Expedit + "condition": ( + int(row[33]) if row[33] is not None else 1 + ), # DO_Condition + "tarif": int(row[34]) if row[34] is not None else 1, # DO_Tarif + "type_frais": ( + int(row[35]) if row[35] is not None else 0 + ), # DO_TypeFrais + "valeur_frais": float(row[36]) if row[36] else 0.0, # DO_ValFrais + "type_franco": ( + int(row[37]) if row[37] is not None else 0 + ), # DO_TypeFranco + "valeur_franco": float(row[38]) if row[38] else 0.0, # DO_ValFranco + } + + cursor.execute( + """ + SELECT + dl.*, + a.AR_Design, a.FA_CodeFamille, a.AR_PrixTTC, a.AR_PrixVen, a.AR_PrixAch, + a.AR_Gamme1, a.AR_Gamme2, a.AR_CodeBarre, a.AR_CoutStd, + a.AR_PoidsNet, a.AR_PoidsBrut, a.AR_UniteVen, + a.AR_Type, a.AR_Nature, a.AR_Escompte, a.AR_Garantie + FROM F_DOCLIGNE dl + LEFT JOIN F_ARTICLE a ON dl.AR_Ref = a.AR_Ref + WHERE dl.DO_Piece = ? AND dl.DO_Type = ? + ORDER BY dl.DL_Ligne + """, + (numero, type_doc), + ) + + lignes = [] + for ligne_row in cursor.fetchall(): + montant_ht = ( + float(ligne_row.DL_MontantHT) if ligne_row.DL_MontantHT else 0.0 + ) + montant_net = ( + float(ligne_row.DL_MontantNet) + if hasattr(ligne_row, "DL_MontantNet") + and ligne_row.DL_MontantNet + else montant_ht + ) + + taux_taxe1 = ( + float(ligne_row.DL_Taxe1) + if hasattr(ligne_row, "DL_Taxe1") and ligne_row.DL_Taxe1 + else 0.0 + ) + taux_taxe2 = ( + float(ligne_row.DL_Taxe2) + if hasattr(ligne_row, "DL_Taxe2") and ligne_row.DL_Taxe2 + else 0.0 + ) + taux_taxe3 = ( + float(ligne_row.DL_Taxe3) + if hasattr(ligne_row, "DL_Taxe3") and ligne_row.DL_Taxe3 + else 0.0 + ) + + total_taux_taxes = taux_taxe1 + taux_taxe2 + taux_taxe3 + montant_ttc = montant_net * (1 + total_taux_taxes / 100) + + montant_taxe1 = montant_net * (taux_taxe1 / 100) + montant_taxe2 = montant_net * (taux_taxe2 / 100) + montant_taxe3 = montant_net * (taux_taxe3 / 100) + + ligne = { + "numero_ligne": ( + int(ligne_row.DL_Ligne) if ligne_row.DL_Ligne else 0 + ), + "article_code": _safe_strip(ligne_row.AR_Ref), + "designation": _safe_strip(ligne_row.DL_Design), + "designation_article": _safe_strip(ligne_row.AR_Design), + "quantite": ( + float(ligne_row.DL_Qte) if ligne_row.DL_Qte else 0.0 + ), + "quantite_livree": ( + float(ligne_row.DL_QteLiv) + if hasattr(ligne_row, "DL_QteLiv") and ligne_row.DL_QteLiv + else 0.0 + ), + "quantite_reservee": ( + float(ligne_row.DL_QteRes) + if hasattr(ligne_row, "DL_QteRes") and ligne_row.DL_QteRes + else 0.0 + ), + "unite": ( + _safe_strip(ligne_row.DL_Unite) + if hasattr(ligne_row, "DL_Unite") + else "" + ), + "prix_unitaire_ht": ( + float(ligne_row.DL_PrixUnitaire) + if ligne_row.DL_PrixUnitaire + else 0.0 + ), + "prix_unitaire_achat": ( + float(ligne_row.AR_PrixAch) if ligne_row.AR_PrixAch else 0.0 + ), + "prix_unitaire_vente": ( + float(ligne_row.AR_PrixVen) if ligne_row.AR_PrixVen else 0.0 + ), + "prix_unitaire_ttc": ( + float(ligne_row.AR_PrixTTC) if ligne_row.AR_PrixTTC else 0.0 + ), + "montant_ligne_ht": montant_ht, + "montant_ligne_net": montant_net, + "montant_ligne_ttc": montant_ttc, + "remise_valeur1": ( + float(ligne_row.DL_Remise01REM_Valeur) + if hasattr(ligne_row, "DL_Remise01REM_Valeur") + and ligne_row.DL_Remise01REM_Valeur + else 0.0 + ), + "remise_type1": ( + int(ligne_row.DL_Remise01REM_Type) + if hasattr(ligne_row, "DL_Remise01REM_Type") + and ligne_row.DL_Remise01REM_Type + else 0 + ), + "remise_valeur2": ( + float(ligne_row.DL_Remise02REM_Valeur) + if hasattr(ligne_row, "DL_Remise02REM_Valeur") + and ligne_row.DL_Remise02REM_Valeur + else 0.0 + ), + "remise_type2": ( + int(ligne_row.DL_Remise02REM_Type) + if hasattr(ligne_row, "DL_Remise02REM_Type") + and ligne_row.DL_Remise02REM_Type + else 0 + ), + "remise_article": ( + float(ligne_row.AR_Escompte) + if ligne_row.AR_Escompte + else 0.0 + ), + "taux_taxe1": taux_taxe1, + "montant_taxe1": montant_taxe1, + "taux_taxe2": taux_taxe2, + "montant_taxe2": montant_taxe2, + "taux_taxe3": taux_taxe3, + "montant_taxe3": montant_taxe3, + "total_taxes": montant_taxe1 + montant_taxe2 + montant_taxe3, + "famille_article": _safe_strip(ligne_row.FA_CodeFamille), + "gamme1": _safe_strip(ligne_row.AR_Gamme1), + "gamme2": _safe_strip(ligne_row.AR_Gamme2), + "code_barre": _safe_strip(ligne_row.AR_CodeBarre), + "type_article": _safe_strip(ligne_row.AR_Type), + "nature_article": _safe_strip(ligne_row.AR_Nature), + "garantie": _safe_strip(ligne_row.AR_Garantie), + "cout_standard": ( + float(ligne_row.AR_CoutStd) if ligne_row.AR_CoutStd else 0.0 + ), + "poids_net": ( + float(ligne_row.AR_PoidsNet) + if ligne_row.AR_PoidsNet + else 0.0 + ), + "poids_brut": ( + float(ligne_row.AR_PoidsBrut) + if ligne_row.AR_PoidsBrut + else 0.0 + ), + "unite_vente": _safe_strip(ligne_row.AR_UniteVen), + "date_livraison_ligne": ( + str(ligne_row.DL_DateLivr) + if hasattr(ligne_row, "DL_DateLivr") + and ligne_row.DL_DateLivr + else "" + ), + "statut_ligne": ( + int(ligne_row.DL_Statut) + if hasattr(ligne_row, "DL_Statut") + and ligne_row.DL_Statut is not None + else 0 + ), + "depot": ( + _safe_strip(ligne_row.DE_No) + if hasattr(ligne_row, "DE_No") + else "" + ), + "numero_commande": ( + _safe_strip(ligne_row.DL_NoColis) + if hasattr(ligne_row, "DL_NoColis") + else "" + ), + "num_colis": ( + _safe_strip(ligne_row.DL_Colis) + if hasattr(ligne_row, "DL_Colis") + else "" + ), + } + lignes.append(ligne) + + doc["lignes"] = lignes + doc["nb_lignes"] = len(lignes) + + total_ht_calcule = sum(l.get("montant_ligne_ht", 0) for l in lignes) + total_ttc_calcule = sum(l.get("montant_ligne_ttc", 0) for l in lignes) + total_taxes_calcule = sum(l.get("total_taxes", 0) for l in lignes) + + doc["total_ht_calcule"] = total_ht_calcule + doc["total_ttc_calcule"] = total_ttc_calcule + doc["total_taxes_calcule"] = total_taxes_calcule + + return doc + + except Exception as e: + logger.error(f" Erreur SQL lecture document {numero}: {e}", exc_info=True) + return None + +def _lister_documents_avec_lignes_sql( + cursor, + type_doc: int, + filtre: str = "", + limit: int = None, +): + """Liste les documents avec leurs lignes.""" + try: + type_doc_sql = _convertir_type_pour_sql(type_doc) + logger.info(f"[SQL LIST] ═══ Type COM {type_doc} → SQL {type_doc_sql} ═══") + + query = """ + SELECT DISTINCT + d.DO_Piece, d.DO_Type, d.DO_Date, d.DO_Ref, d.DO_Tiers, + d.DO_TotalHT, d.DO_TotalTTC, d.DO_NetAPayer, d.DO_Statut, + d.DO_DateLivr, d.DO_DateExpedition, d.DO_Contact, d.DO_TotalHTNet, + d.DO_MontantRegle, d.DO_Reliquat, d.DO_TxEscompte, d.DO_Escompte, + d.DO_Taxe1, d.DO_Taxe2, d.DO_Taxe3, + d.DO_CodeTaxe1, d.DO_CodeTaxe2, d.DO_CodeTaxe3, + d.DO_EStatut, d.DO_Imprim, d.DO_Valide, d.DO_Cloture, d.DO_Transfere, + d.DO_Souche, d.DO_PieceOrig, d.DO_GUID, + d.CA_Num, d.CG_Num, d.DO_Expedit, d.DO_Condition, d.DO_Tarif, + d.DO_TypeFrais, d.DO_ValFrais, d.DO_TypeFranco, d.DO_ValFranco, + c.CT_Intitule, c.CT_Adresse, c.CT_CodePostal, + c.CT_Ville, c.CT_Telephone, c.CT_EMail + FROM F_DOCENTETE d + LEFT JOIN F_COMPTET c ON d.DO_Tiers = c.CT_Num + WHERE d.DO_Type = ? + """ + + params = [type_doc_sql] + + if filtre: + query += " AND (d.DO_Piece LIKE ? OR c.CT_Intitule LIKE ? OR d.DO_Ref LIKE ?)" + params.extend([f"%{filtre}%", f"%{filtre}%", f"%{filtre}%"]) + + query += " ORDER BY d.DO_Date DESC" + + if limit: + query = f"SELECT TOP ({limit}) * FROM ({query}) AS subquery" + + cursor.execute(query, params) + entetes = cursor.fetchall() + + logger.info(f"[SQL LIST] {len(entetes)} documents SQL") + + documents = [] + stats = { + "total": len(entetes), + "exclus_prefixe": 0, + "erreur_construction": 0, + "erreur_lignes": 0, + "erreur_transformations": 0, + "erreur_liaisons": 0, + "succes": 0, + } + + for idx, entete in enumerate(entetes): + numero = _safe_strip(entete.DO_Piece) + logger.info( + f"[SQL LIST] [{idx+1}/{len(entetes)}] Traitement {numero}..." + ) + + try: + prefixes_vente = { + 0: ["DE"], + 10: ["BC"], + 30: ["BL"], + 50: ["AV", "AR"], + 60: ["FA", "FC"], + } + + prefixes_acceptes = prefixes_vente.get(type_doc, []) + + if prefixes_acceptes: + est_vente = any( + numero.upper().startswith(p) for p in prefixes_acceptes + ) + if not est_vente: + logger.info( + f"[SQL LIST] {numero} : exclu (préfixe achat)" + ) + stats["exclus_prefixe"] += 1 + continue + + logger.debug(f"[SQL LIST] {numero} : préfixe OK") + + try: + type_doc_depuis_sql = _convertir_type_depuis_sql( + int(entete.DO_Type) + ) + + doc = { + "numero": numero, + "type": type_doc_depuis_sql, + "reference": _safe_strip(entete.DO_Ref), + "date": str(entete.DO_Date) if entete.DO_Date else "", + "date_livraison": ( + str(entete.DO_DateLivr) + if entete.DO_DateLivr + else "" + ), + "date_expedition": ( + str(entete.DO_DateExpedition) + if entete.DO_DateExpedition + else "" + ), + "client_code": _safe_strip(entete.DO_Tiers), + "client_intitule": _safe_strip(entete.CT_Intitule), + "client_adresse": _safe_strip(entete.CT_Adresse), + "client_code_postal": _safe_strip( + entete.CT_CodePostal + ), + "client_ville": _safe_strip(entete.CT_Ville), + "client_telephone": _safe_strip( + entete.CT_Telephone + ), + "client_email": _safe_strip(entete.CT_EMail), + "contact": _safe_strip(entete.DO_Contact), + "total_ht": ( + float(entete.DO_TotalHT) + if entete.DO_TotalHT + else 0.0 + ), + "total_ht_net": ( + float(entete.DO_TotalHTNet) + if entete.DO_TotalHTNet + else 0.0 + ), + "total_ttc": ( + float(entete.DO_TotalTTC) + if entete.DO_TotalTTC + else 0.0 + ), + "net_a_payer": ( + float(entete.DO_NetAPayer) + if entete.DO_NetAPayer + else 0.0 + ), + "montant_regle": ( + float(entete.DO_MontantRegle) + if entete.DO_MontantRegle + else 0.0 + ), + "reliquat": ( + float(entete.DO_Reliquat) + if entete.DO_Reliquat + else 0.0 + ), + "taux_escompte": ( + float(entete.DO_TxEscompte) + if entete.DO_TxEscompte + else 0.0 + ), + "escompte": ( + float(entete.DO_Escompte) + if entete.DO_Escompte + else 0.0 + ), + "taxe1": ( + float(entete.DO_Taxe1) if entete.DO_Taxe1 else 0.0 + ), + "taxe2": ( + float(entete.DO_Taxe2) if entete.DO_Taxe2 else 0.0 + ), + "taxe3": ( + float(entete.DO_Taxe3) if entete.DO_Taxe3 else 0.0 + ), + "code_taxe1": _safe_strip(entete.DO_CodeTaxe1), + "code_taxe2": _safe_strip(entete.DO_CodeTaxe2), + "code_taxe3": _safe_strip(entete.DO_CodeTaxe3), + "statut": ( + int(entete.DO_Statut) + if entete.DO_Statut is not None + else 0 + ), + "statut_estatut": ( + int(entete.DO_EStatut) + if entete.DO_EStatut is not None + else 0 + ), + "imprime": ( + int(entete.DO_Imprim) + if entete.DO_Imprim is not None + else 0 + ), + "valide": ( + int(entete.DO_Valide) + if entete.DO_Valide is not None + else 0 + ), + "cloture": ( + int(entete.DO_Cloture) + if entete.DO_Cloture is not None + else 0 + ), + "transfere": ( + int(entete.DO_Transfere) + if entete.DO_Transfere is not None + else 0 + ), + "souche": _safe_strip(entete.DO_Souche), + "piece_origine": _safe_strip(entete.DO_PieceOrig), + "guid": _safe_strip(entete.DO_GUID), + "ca_num": _safe_strip(entete.CA_Num), + "cg_num": _safe_strip(entete.CG_Num), + "expedition": _safe_strip(entete.DO_Expedit), + "condition": _safe_strip(entete.DO_Condition), + "tarif": _safe_strip(entete.DO_Tarif), + "type_frais": ( + int(entete.DO_TypeFrais) + if entete.DO_TypeFrais is not None + else 0 + ), + "valeur_frais": ( + float(entete.DO_ValFrais) + if entete.DO_ValFrais + else 0.0 + ), + "type_franco": ( + int(entete.DO_TypeFranco) + if entete.DO_TypeFranco is not None + else 0 + ), + "valeur_franco": ( + float(entete.DO_ValFranco) + if entete.DO_ValFranco + else 0.0 + ), + "lignes": [], + } + + logger.debug( + f"[SQL LIST] {numero} : document de base créé" + ) + + except Exception as e: + logger.error( + f"[SQL LIST] {numero} : ERREUR construction base: {e}", + exc_info=True, + ) + stats["erreur_construction"] += 1 + continue + + try: + cursor.execute( + """ + SELECT dl.*, + a.AR_Design, a.FA_CodeFamille, a.AR_PrixTTC, a.AR_PrixVen, a.AR_PrixAch, + a.AR_Gamme1, a.AR_Gamme2, a.AR_CodeBarre, a.AR_CoutStd, + a.AR_PoidsNet, a.AR_PoidsBrut, a.AR_UniteVen, + a.AR_Type, a.AR_Nature, a.AR_Escompte, a.AR_Garantie + FROM F_DOCLIGNE dl + LEFT JOIN F_ARTICLE a ON dl.AR_Ref = a.AR_Ref + WHERE dl.DO_Piece = ? AND dl.DO_Type = ? + ORDER BY dl.DL_Ligne + """, + (numero, type_doc_sql), + ) + + for ligne_row in cursor.fetchall(): + montant_ht = ( + float(ligne_row.DL_MontantHT) + if ligne_row.DL_MontantHT + else 0.0 + ) + montant_net = ( + float(ligne_row.DL_MontantNet) + if hasattr(ligne_row, "DL_MontantNet") + and ligne_row.DL_MontantNet + else montant_ht + ) + + taux_taxe1 = ( + float(ligne_row.DL_Taxe1) + if hasattr(ligne_row, "DL_Taxe1") + and ligne_row.DL_Taxe1 + else 0.0 + ) + taux_taxe2 = ( + float(ligne_row.DL_Taxe2) + if hasattr(ligne_row, "DL_Taxe2") + and ligne_row.DL_Taxe2 + else 0.0 + ) + taux_taxe3 = ( + float(ligne_row.DL_Taxe3) + if hasattr(ligne_row, "DL_Taxe3") + and ligne_row.DL_Taxe3 + else 0.0 + ) + + total_taux_taxes = taux_taxe1 + taux_taxe2 + taux_taxe3 + montant_ttc = montant_net * (1 + total_taux_taxes / 100) + + montant_taxe1 = montant_net * (taux_taxe1 / 100) + montant_taxe2 = montant_net * (taux_taxe2 / 100) + montant_taxe3 = montant_net * (taux_taxe3 / 100) + + ligne = { + "numero_ligne": ( + int(ligne_row.DL_Ligne) + if ligne_row.DL_Ligne + else 0 + ), + "article_code": _safe_strip(ligne_row.AR_Ref), + "designation": _safe_strip( + ligne_row.DL_Design + ), + "designation_article": _safe_strip( + ligne_row.AR_Design + ), + "quantite": ( + float(ligne_row.DL_Qte) + if ligne_row.DL_Qte + else 0.0 + ), + "quantite_livree": ( + float(ligne_row.DL_QteLiv) + if hasattr(ligne_row, "DL_QteLiv") + and ligne_row.DL_QteLiv + else 0.0 + ), + "quantite_reservee": ( + float(ligne_row.DL_QteRes) + if hasattr(ligne_row, "DL_QteRes") + and ligne_row.DL_QteRes + else 0.0 + ), + "unite": ( + _safe_strip(ligne_row.DL_Unite) + if hasattr(ligne_row, "DL_Unite") + else "" + ), + "prix_unitaire_ht": ( + float(ligne_row.DL_PrixUnitaire) + if ligne_row.DL_PrixUnitaire + else 0.0 + ), + "prix_unitaire_achat": ( + float(ligne_row.AR_PrixAch) + if ligne_row.AR_PrixAch + else 0.0 + ), + "prix_unitaire_vente": ( + float(ligne_row.AR_PrixVen) + if ligne_row.AR_PrixVen + else 0.0 + ), + "prix_unitaire_ttc": ( + float(ligne_row.AR_PrixTTC) + if ligne_row.AR_PrixTTC + else 0.0 + ), + "montant_ligne_ht": montant_ht, + "montant_ligne_net": montant_net, + "montant_ligne_ttc": montant_ttc, + "remise_valeur1": ( + float(ligne_row.DL_Remise01REM_Valeur) + if hasattr(ligne_row, "DL_Remise01REM_Valeur") + and ligne_row.DL_Remise01REM_Valeur + else 0.0 + ), + "remise_type1": ( + int(ligne_row.DL_Remise01REM_Type) + if hasattr(ligne_row, "DL_Remise01REM_Type") + and ligne_row.DL_Remise01REM_Type + else 0 + ), + "remise_valeur2": ( + float(ligne_row.DL_Remise02REM_Valeur) + if hasattr(ligne_row, "DL_Remise02REM_Valeur") + and ligne_row.DL_Remise02REM_Valeur + else 0.0 + ), + "remise_type2": ( + int(ligne_row.DL_Remise02REM_Type) + if hasattr(ligne_row, "DL_Remise02REM_Type") + and ligne_row.DL_Remise02REM_Type + else 0 + ), + "remise_article": ( + float(ligne_row.AR_Escompte) + if ligne_row.AR_Escompte + else 0.0 + ), + "taux_taxe1": taux_taxe1, + "montant_taxe1": montant_taxe1, + "taux_taxe2": taux_taxe2, + "montant_taxe2": montant_taxe2, + "taux_taxe3": taux_taxe3, + "montant_taxe3": montant_taxe3, + "total_taxes": montant_taxe1 + + montant_taxe2 + + montant_taxe3, + "famille_article": _safe_strip( + ligne_row.FA_CodeFamille + ), + "gamme1": _safe_strip(ligne_row.AR_Gamme1), + "gamme2": _safe_strip(ligne_row.AR_Gamme2), + "code_barre": _safe_strip( + ligne_row.AR_CodeBarre + ), + "type_article": _safe_strip(ligne_row.AR_Type), + "nature_article": _safe_strip( + ligne_row.AR_Nature + ), + "garantie": _safe_strip(ligne_row.AR_Garantie), + "cout_standard": ( + float(ligne_row.AR_CoutStd) + if ligne_row.AR_CoutStd + else 0.0 + ), + "poids_net": ( + float(ligne_row.AR_PoidsNet) + if ligne_row.AR_PoidsNet + else 0.0 + ), + "poids_brut": ( + float(ligne_row.AR_PoidsBrut) + if ligne_row.AR_PoidsBrut + else 0.0 + ), + "unite_vente": _safe_strip( + ligne_row.AR_UniteVen + ), + "date_livraison_ligne": ( + str(ligne_row.DL_DateLivr) + if hasattr(ligne_row, "DL_DateLivr") + and ligne_row.DL_DateLivr + else "" + ), + "statut_ligne": ( + int(ligne_row.DL_Statut) + if hasattr(ligne_row, "DL_Statut") + and ligne_row.DL_Statut is not None + else 0 + ), + "depot": ( + _safe_strip(ligne_row.DE_No) + if hasattr(ligne_row, "DE_No") + else "" + ), + "numero_commande": ( + _safe_strip(ligne_row.DL_NoColis) + if hasattr(ligne_row, "DL_NoColis") + else "" + ), + "num_colis": ( + _safe_strip(ligne_row.DL_Colis) + if hasattr(ligne_row, "DL_Colis") + else "" + ), + } + doc["lignes"].append(ligne) + + doc["nb_lignes"] = len(doc["lignes"]) + doc["total_ht_calcule"] = sum( + l.get("montant_ligne_ht", 0) for l in doc["lignes"] + ) + doc["total_ttc_calcule"] = sum( + l.get("montant_ligne_ttc", 0) for l in doc["lignes"] + ) + doc["total_taxes_calcule"] = sum( + l.get("total_taxes", 0) for l in doc["lignes"] + ) + + logger.debug( + f"[SQL LIST] {numero} : {doc['nb_lignes']} lignes chargées" + ) + + except Exception as e: + logger.error( + f"[SQL LIST] {numero} : ERREUR lignes: {e}", + exc_info=True, + ) + stats["erreur_lignes"] += 1 + + documents.append(doc) + stats["succes"] += 1 + logger.info( + f"[SQL LIST] {numero} : AJOUTÉ à la liste (total: {len(documents)})" + ) + + except Exception as e: + logger.error( + f"[SQL LIST] {numero} : EXCEPTION GLOBALE - DOCUMENT EXCLU: {e}", + exc_info=True, + ) + continue + + logger.info(f"[SQL LIST] ═══════════════════════════") + logger.info(f"[SQL LIST] STATISTIQUES FINALES:") + logger.info(f"[SQL LIST] Total SQL: {stats['total']}") + logger.info(f"[SQL LIST] Exclus préfixe: {stats['exclus_prefixe']}") + logger.info( + f"[SQL LIST] Erreur construction: {stats['erreur_construction']}" + ) + logger.info(f"[SQL LIST] Erreur lignes: {stats['erreur_lignes']}") + logger.info( + f"[SQL LIST] Erreur transformations: {stats['erreur_transformations']}" + ) + logger.info(f"[SQL LIST] Erreur liaisons: {stats['erreur_liaisons']}") + logger.info(f"[SQL LIST] SUCCÈS: {stats['succes']}") + logger.info(f"[SQL LIST] Documents retournés: {len(documents)}") + logger.info(f"[SQL LIST] ═══════════════════════════") + + return documents + + except Exception as e: + logger.error(f" Erreur GLOBALE listage: {e}", exc_info=True) + return [] + + +__all__ = [ + "_afficher_etat_document", + "_compter_lignes_document", + "_rechercher_devis_par_numero", + "_lire_document_sql", + "_lister_documents_avec_lignes_sql", +] \ No newline at end of file diff --git a/utils/functions/functions.py b/utils/functions/functions.py index dae1a57..05b1bfa 100644 --- a/utils/functions/functions.py +++ b/utils/functions/functions.py @@ -1,4 +1,5 @@ from typing import Optional +from datetime import datetime, timedelta, date import logging logger = logging.getLogger(__name__) @@ -77,12 +78,12 @@ def _get_type_libelle(type_doc: int) -> str: return f"Type {type_doc}" -def _convertir_type_pour_sql(self, type_doc: int) -> int: +def _convertir_type_pour_sql(type_doc: int) -> int: """COM → SQL : 0, 10, 20, 30... → 0, 1, 2, 3...""" mapping = {0: 0, 10: 1, 20: 2, 30: 3, 40: 4, 50: 5, 60: 6} return mapping.get(type_doc, type_doc) -def _convertir_type_depuis_sql(self, type_sql: int) -> int: +def _convertir_type_depuis_sql(type_sql: int) -> int: """SQL → COM : 0, 1, 2, 3... → 0, 10, 20, 30...""" mapping = {0: 0, 1: 10, 2: 20, 3: 30, 4: 40, 5: 50, 6: 60} return mapping.get(type_sql, type_sql) @@ -105,6 +106,22 @@ def _normaliser_type_document(type_doc: int) -> int: return mapping_normalisation.get(type_doc, type_doc) +def normaliser_date(valeur): + if isinstance(valeur, str): + try: + return datetime.fromisoformat(valeur) + except ValueError: + return datetime.now() + + elif isinstance(valeur, date): + return datetime.combine(valeur, datetime.min.time()) + + elif isinstance(valeur, datetime): + return valeur + + else: + return datetime.now() + __all__ = [ "_clean_str", "_safe_strip", @@ -113,6 +130,6 @@ __all__ = [ "_get_type_libelle", "_normaliser_type_document", "_convertir_type_depuis_sql", - "_convertir_type_pour_sql" - + "_convertir_type_pour_sql", + "normaliser_date" ] \ No newline at end of file diff --git a/utils/tiers/contacts/contacts.py b/utils/tiers/contacts/contacts.py index d833fe7..7b2d8ef 100644 --- a/utils/tiers/contacts/contacts.py +++ b/utils/tiers/contacts/contacts.py @@ -1,37 +1,62 @@ from typing import Dict, List, Optional, Any from utils.functions.items_to_dict import _row_to_contact_dict import logging +from utils.functions.functions import _safe_strip logger = logging.getLogger(__name__) - - -def obtenir_contact(self, numero: str, contact_numero: int) -> Optional[Dict]: - """ - Récupère un contact spécifique par son CT_No - """ - try: - with self._get_sql_connection() as conn: - cursor = conn.cursor() - - query = """ - SELECT - CT_Num, CT_No, N_Contact, - CT_Civilite, CT_Nom, CT_Prenom, CT_Fonction, - N_Service, - CT_Telephone, CT_TelPortable, CT_Telecopie, CT_EMail, - CT_Facebook, CT_LinkedIn, CT_Skype - FROM F_CONTACTT - WHERE CT_Num = ? AND CT_No = ? - """ - - cursor.execute(query, [numero, contact_numero]) - row = cursor.fetchone() - - if not row: - return None - - return _row_to_contact_dict(row) +def _get_contacts_client(numero: str, conn) -> list: + try: + cursor = conn.cursor() + + query = """ + SELECT + CT_Num, CT_No, N_Contact, + CT_Civilite, CT_Nom, CT_Prenom, CT_Fonction, + N_Service, + CT_Telephone, CT_TelPortable, CT_Telecopie, CT_EMail, + CT_Facebook, CT_LinkedIn, CT_Skype + FROM F_CONTACTT + WHERE CT_Num = ? + ORDER BY N_Contact, CT_Nom, CT_Prenom + """ + + cursor.execute(query, [numero]) + rows = cursor.fetchall() + + query_client = """ + SELECT CT_Contact + FROM F_COMPTET + WHERE CT_Num = ? + """ + cursor.execute(query_client, [numero]) + client_row = cursor.fetchone() + + nom_contact_defaut = None + if client_row: + nom_contact_defaut = _safe_strip(client_row.CT_Contact) + + contacts = [] + for row in rows: + contact = _row_to_contact_dict(row) + + if nom_contact_defaut: + nom_complet = f"{contact.get('prenom', '')} {contact['nom']}".strip() + contact["est_defaut"] = ( + nom_complet == nom_contact_defaut or + contact['nom'] == nom_contact_defaut + ) + else: + contact["est_defaut"] = False + + contacts.append(contact) + + return contacts + except Exception as e: - logger.error(f"Erreur obtention contact: {e}") - raise RuntimeError(f"Erreur lecture contact: {str(e)}") + logger.warning(f" Impossible de récupérer contacts pour {numero}: {e}") + return [] + +__all__ = [ + "_get_contacts_client" +] \ No newline at end of file diff --git a/utils/tiers/fournisseurs/fournisseurs_extraction.py b/utils/tiers/fournisseurs/fournisseurs_extraction.py new file mode 100644 index 0000000..2933942 --- /dev/null +++ b/utils/tiers/fournisseurs/fournisseurs_extraction.py @@ -0,0 +1,383 @@ + +import win32com.client +import logging + +logger = logging.getLogger(__name__) + +def _extraire_fournisseur_enrichi(fourn_obj): + try: + numero = getattr(fourn_obj, "CT_Num", "").strip() + if not numero: + return None + + intitule = getattr(fourn_obj, "CT_Intitule", "").strip() + + data = { + "numero": numero, + "intitule": intitule, + "type": 1, # Fournisseur + "est_fournisseur": True, + } + + try: + sommeil = getattr(fourn_obj, "CT_Sommeil", 0) + data["est_actif"] = sommeil == 0 + data["en_sommeil"] = sommeil == 1 + except: + data["est_actif"] = True + data["en_sommeil"] = False + + try: + adresse_obj = getattr(fourn_obj, "Adresse", None) + if adresse_obj: + data["adresse"] = getattr(adresse_obj, "Adresse", "").strip() + data["complement"] = getattr(adresse_obj, "Complement", "").strip() + data["code_postal"] = getattr(adresse_obj, "CodePostal", "").strip() + data["ville"] = getattr(adresse_obj, "Ville", "").strip() + data["region"] = getattr(adresse_obj, "Region", "").strip() + data["pays"] = getattr(adresse_obj, "Pays", "").strip() + + parties_adresse = [] + if data["adresse"]: + parties_adresse.append(data["adresse"]) + if data["complement"]: + parties_adresse.append(data["complement"]) + if data["code_postal"] or data["ville"]: + ville_cp = f"{data['code_postal']} {data['ville']}".strip() + if ville_cp: + parties_adresse.append(ville_cp) + if data["pays"]: + parties_adresse.append(data["pays"]) + + data["adresse_complete"] = ", ".join(parties_adresse) + else: + data["adresse"] = "" + data["complement"] = "" + data["code_postal"] = "" + data["ville"] = "" + data["region"] = "" + data["pays"] = "" + data["adresse_complete"] = "" + except Exception as e: + logger.debug(f"Erreur adresse fournisseur {numero}: {e}") + data["adresse"] = "" + data["complement"] = "" + data["code_postal"] = "" + data["ville"] = "" + data["region"] = "" + data["pays"] = "" + data["adresse_complete"] = "" + + try: + telecom_obj = getattr(fourn_obj, "Telecom", None) + if telecom_obj: + data["telephone"] = getattr(telecom_obj, "Telephone", "").strip() + data["portable"] = getattr(telecom_obj, "Portable", "").strip() + data["telecopie"] = getattr(telecom_obj, "Telecopie", "").strip() + data["email"] = getattr(telecom_obj, "EMail", "").strip() + + try: + site = ( + getattr(telecom_obj, "Site", None) + or getattr(telecom_obj, "Web", None) + or getattr(telecom_obj, "SiteWeb", "") + ) + data["site_web"] = str(site).strip() if site else "" + except: + data["site_web"] = "" + else: + data["telephone"] = "" + data["portable"] = "" + data["telecopie"] = "" + data["email"] = "" + data["site_web"] = "" + except Exception as e: + logger.debug(f"Erreur telecom fournisseur {numero}: {e}") + data["telephone"] = "" + data["portable"] = "" + data["telecopie"] = "" + data["email"] = "" + data["site_web"] = "" + + try: + data["siret"] = getattr(fourn_obj, "CT_Siret", "").strip() + except: + data["siret"] = "" + + try: + if data["siret"] and len(data["siret"]) >= 9: + data["siren"] = data["siret"][:9] + else: + data["siren"] = getattr(fourn_obj, "CT_Siren", "").strip() + except: + data["siren"] = "" + + try: + data["tva_intra"] = getattr(fourn_obj, "CT_Identifiant", "").strip() + except: + data["tva_intra"] = "" + + try: + data["code_naf"] = ( + getattr(fourn_obj, "CT_CodeNAF", "").strip() + or getattr(fourn_obj, "CT_APE", "").strip() + ) + except: + data["code_naf"] = "" + + try: + data["forme_juridique"] = getattr( + fourn_obj, "CT_FormeJuridique", "" + ).strip() + except: + data["forme_juridique"] = "" + + try: + cat_tarif = getattr(fourn_obj, "N_CatTarif", None) + data["categorie_tarifaire"] = ( + int(cat_tarif) if cat_tarif is not None else None + ) + except: + data["categorie_tarifaire"] = None + + try: + cat_compta = getattr(fourn_obj, "N_CatCompta", None) + data["categorie_comptable"] = ( + int(cat_compta) if cat_compta is not None else None + ) + except: + data["categorie_comptable"] = None + + try: + cond_regl = getattr(fourn_obj, "CT_CondRegl", "").strip() + data["conditions_reglement_code"] = cond_regl + + if cond_regl: + try: + cond_obj = getattr(fourn_obj, "ConditionReglement", None) + if cond_obj: + cond_obj.Read() + data["conditions_reglement_libelle"] = getattr( + cond_obj, "C_Intitule", "" + ).strip() + else: + data["conditions_reglement_libelle"] = "" + except: + data["conditions_reglement_libelle"] = "" + else: + data["conditions_reglement_libelle"] = "" + except: + data["conditions_reglement_code"] = "" + data["conditions_reglement_libelle"] = "" + + try: + mode_regl = getattr(fourn_obj, "CT_ModeRegl", "").strip() + data["mode_reglement_code"] = mode_regl + + if mode_regl: + try: + mode_obj = getattr(fourn_obj, "ModeReglement", None) + if mode_obj: + mode_obj.Read() + data["mode_reglement_libelle"] = getattr( + mode_obj, "M_Intitule", "" + ).strip() + else: + data["mode_reglement_libelle"] = "" + except: + data["mode_reglement_libelle"] = "" + else: + data["mode_reglement_libelle"] = "" + except: + data["mode_reglement_code"] = "" + data["mode_reglement_libelle"] = "" + + data["coordonnees_bancaires"] = [] + + try: + factory_banque = getattr(fourn_obj, "FactoryBanque", None) + + if factory_banque: + index = 1 + while index <= 5: # Max 5 comptes bancaires + try: + banque_persist = factory_banque.List(index) + if banque_persist is None: + break + + banque = win32com.client.CastTo( + banque_persist, "IBOBanque3" + ) + banque.Read() + + compte_bancaire = { + "banque_nom": getattr( + banque, "BI_Intitule", "" + ).strip(), + "iban": getattr(banque, "RIB_Iban", "").strip(), + "bic": getattr(banque, "RIB_Bic", "").strip(), + "code_banque": getattr( + banque, "RIB_Banque", "" + ).strip(), + "code_guichet": getattr( + banque, "RIB_Guichet", "" + ).strip(), + "numero_compte": getattr( + banque, "RIB_Compte", "" + ).strip(), + "cle_rib": getattr(banque, "RIB_Cle", "").strip(), + } + + if ( + compte_bancaire["iban"] + or compte_bancaire["numero_compte"] + ): + data["coordonnees_bancaires"].append(compte_bancaire) + + index += 1 + except: + break + except Exception as e: + logger.debug( + f"Erreur coordonnées bancaires fournisseur {numero}: {e}" + ) + + if data["coordonnees_bancaires"]: + data["iban_principal"] = data["coordonnees_bancaires"][0].get( + "iban", "" + ) + data["bic_principal"] = data["coordonnees_bancaires"][0].get("bic", "") + else: + data["iban_principal"] = "" + data["bic_principal"] = "" + + data["contacts"] = [] + + try: + factory_contact = getattr(fourn_obj, "FactoryContact", None) + + if factory_contact: + index = 1 + while index <= 20: # Max 20 contacts + try: + contact_persist = factory_contact.List(index) + if contact_persist is None: + break + + contact = win32com.client.CastTo( + contact_persist, "IBOContact3" + ) + contact.Read() + + contact_data = { + "nom": getattr(contact, "CO_Nom", "").strip(), + "prenom": getattr(contact, "CO_Prenom", "").strip(), + "fonction": getattr(contact, "CO_Fonction", "").strip(), + "service": getattr(contact, "CO_Service", "").strip(), + "telephone": getattr( + contact, "CO_Telephone", "" + ).strip(), + "portable": getattr(contact, "CO_Portable", "").strip(), + "email": getattr(contact, "CO_EMail", "").strip(), + } + + nom_complet = f"{contact_data['prenom']} {contact_data['nom']}".strip() + if nom_complet: + contact_data["nom_complet"] = nom_complet + else: + contact_data["nom_complet"] = contact_data["nom"] + + if contact_data["nom"]: + data["contacts"].append(contact_data) + + index += 1 + except: + break + except Exception as e: + logger.debug(f"Erreur contacts fournisseur {numero}: {e}") + + data["nb_contacts"] = len(data["contacts"]) + + if data["contacts"]: + data["contact_principal"] = data["contacts"][0] + else: + data["contact_principal"] = None + + try: + data["encours_autorise"] = float(getattr(fourn_obj, "CT_Encours", 0.0)) + except: + data["encours_autorise"] = 0.0 + + try: + data["ca_annuel"] = float(getattr(fourn_obj, "CT_ChiffreAffaire", 0.0)) + except: + data["ca_annuel"] = 0.0 + + try: + data["compte_general"] = getattr(fourn_obj, "CG_Num", "").strip() + except: + data["compte_general"] = "" + + try: + date_creation = getattr(fourn_obj, "CT_DateCreate", None) + data["date_creation"] = str(date_creation) if date_creation else "" + except: + data["date_creation"] = "" + + try: + date_modif = getattr(fourn_obj, "CT_DateModif", None) + data["date_modification"] = str(date_modif) if date_modif else "" + except: + data["date_modification"] = "" + + return data + + except Exception as e: + logger.error(f" Erreur extraction fournisseur: {e}", exc_info=True) + return { + "numero": getattr(fourn_obj, "CT_Num", "").strip(), + "intitule": getattr(fourn_obj, "CT_Intitule", "").strip(), + "type": 1, + "est_fournisseur": True, + "est_actif": True, + "en_sommeil": False, + "adresse": "", + "complement": "", + "code_postal": "", + "ville": "", + "region": "", + "pays": "", + "adresse_complete": "", + "telephone": "", + "portable": "", + "telecopie": "", + "email": "", + "site_web": "", + "siret": "", + "siren": "", + "tva_intra": "", + "code_naf": "", + "forme_juridique": "", + "categorie_tarifaire": None, + "categorie_comptable": None, + "conditions_reglement_code": "", + "conditions_reglement_libelle": "", + "mode_reglement_code": "", + "mode_reglement_libelle": "", + "iban_principal": "", + "bic_principal": "", + "coordonnees_bancaires": [], + "contacts": [], + "nb_contacts": 0, + "contact_principal": None, + "encours_autorise": 0.0, + "ca_annuel": 0.0, + "compte_general": "", + "date_creation": "", + "date_modification": "", + } + + +__all__ = [ + "_extraire_fournisseur_enrichi" +] \ No newline at end of file