import win32com.client import logging from utils.functions.functions import ( _convertir_type_depuis_sql, _convertir_type_pour_sql, _safe_strip, _combiner_date_heure, ) 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 Exception: 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 Exception: 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 Exception: 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, d.DO_Heure 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" ) 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]), "date": _combiner_date_heure(row[1], row[45]), "date_livraison": str(row[7]) if row[7] else "", "date_expedition": str(row[8]) if row[8] else "", "client_code": _safe_strip(row[6]), "client_intitule": _safe_strip(row[39]), "client_adresse": _safe_strip(row[40]), "client_code_postal": _safe_strip(row[41]), "client_ville": _safe_strip(row[42]), "client_telephone": _safe_strip(row[43]), "client_email": _safe_strip(row[44]), "contact": _safe_strip(row[9]), "total_ht": float(row[3]) if row[3] else 0.0, "total_ht_net": float(row[10]) if row[10] else 0.0, "total_ttc": float(row[4]) if row[4] else 0.0, "net_a_payer": float(row[11]) if row[11] else 0.0, "montant_regle": float(row[12]) if row[12] else 0.0, "reliquat": float(row[13]) if row[13] else 0.0, "taux_escompte": float(row[14]) if row[14] else 0.0, "escompte": float(row[15]) if row[15] else 0.0, "taxe1": float(row[16]) if row[16] else 0.0, "taxe2": float(row[17]) if row[17] else 0.0, "taxe3": float(row[18]) if row[18] else 0.0, "code_taxe1": _safe_strip(row[19]), "code_taxe2": _safe_strip(row[20]), "code_taxe3": _safe_strip(row[21]), "statut": int(row[5]) if row[5] is not None else 0, "statut_estatut": int(row[22]) if row[22] is not None else 0, "imprime": int(row[23]) if row[23] is not None else 0, "valide": int(row[24]) if row[24] is not None else 0, "cloture": int(row[25]) if row[25] is not None else 0, "transfere": int(row[26]) if row[26] is not None else 0, "souche": int(row[27]) if row[27] is not None else 0, "piece_origine": _safe_strip(row[28]), "guid": _safe_strip(row[29]), "ca_num": _safe_strip(row[30]), "cg_num": _safe_strip(row[31]), "expedition": int(row[32]) if row[32] is not None else 1, "condition": int(row[33]) if row[33] is not None else 1, "tarif": int(row[34]) if row[34] is not None else 1, "type_frais": int(row[35]) if row[35] is not None else 0, "valeur_frais": float(row[36]) if row[36] else 0.0, "type_franco": int(row[37]) if row[37] is not None else 0, "valeur_franco": float(row[38]) if row[38] else 0.0, } 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(ligne.get("montant_ligne_ht", 0) for ligne in lignes) total_ttc_calcule = sum(ligne.get("montant_ligne_ttc", 0) for ligne in lignes) total_taxes_calcule = sum(ligne.get("total_taxes", 0) for ligne 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, d.DO_Heure 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, d.DO_Heure 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) 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": _combiner_date_heure(entete.DO_Date, entete.DO_Heure), "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( ligne.get("montant_ligne_ht", 0) for ligne in doc["lignes"] ) doc["total_ttc_calcule"] = sum( ligne.get("montant_ligne_ttc", 0) for ligne in doc["lignes"] ) doc["total_taxes_calcule"] = sum( ligne.get("total_taxes", 0) for ligne 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 except Exception as e: logger.error( f"[SQL LIST] {numero} : EXCEPTION GLOBALE - DOCUMENT EXCLU: {e}", exc_info=True, ) continue 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", ]