From 57103d406dd11a0ac7a57dc3002320c2dd1c1b3f Mon Sep 17 00:00:00 2001 From: fanilo Date: Mon, 5 Jan 2026 17:31:18 +0100 Subject: [PATCH] Unified document's modification function --- sage_connector.py | 2395 +--------------------------- utils/functions/data/create_doc.py | 292 ++++ utils/functions/items_to_dict.py | 32 + 3 files changed, 361 insertions(+), 2358 deletions(-) diff --git a/sage_connector.py b/sage_connector.py index 0816f48..983ab57 100644 --- a/sage_connector.py +++ b/sage_connector.py @@ -94,7 +94,12 @@ from utils import ( ) from schemas.documents.doc_config import TypeDocumentVente -from utils.functions.data.create_doc import creer_document_vente +from utils.functions.data.create_doc import ( + creer_document_vente, + modifier_document_vente, +) + +from utils.functions.items_to_dict import row_to_collaborateur_dict logger = logging.getLogger(__name__) @@ -1277,576 +1282,11 @@ class SageConnector: "statut": statut_final, } - def modifier_devis(self, numero: str, devis_data: Dict) -> Dict: - logger.info("=" * 100) - logger.info("=" * 100) - logger.info(f" NOUVELLE MÉTHODE modifier_devis() APPELÉE POUR {numero} ") - logger.info(f" Données reçues: {devis_data}") - logger.info("=" * 100) - - if not self.cial: - logger.error(" Connexion Sage non établie") - raise RuntimeError("Connexion Sage non établie") - - try: - with ( - self._com_context(), - self._lock_com, - self._get_sql_connection() as conn, - ): - cursor = conn.cursor() - logger.info("") - logger.info("=" * 80) - logger.info(f" [ÉTAPE 1] CHARGEMENT DU DEVIS {numero}") - logger.info("=" * 80) - - doc = self._charger_devis(numero) - logger.info(f" Devis {numero} chargé avec succès") - - logger.info("") - _afficher_etat_document(doc, "📸 ÉTAT INITIAL") - - logger.info(" Vérification statut transformation...") - _verifier_devis_non_transforme(numero, doc, cursor) - logger.info(" Devis non transformé - modification autorisée") - - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 2] ANALYSE DOCUMENT ACTUEL") - logger.info("=" * 80) - - client_code_initial = "" - try: - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_code_initial = getattr(client_obj, "CT_Num", "").strip() - logger.info(f" Client: {client_code_initial}") - else: - logger.warning(" Objet Client non trouvé") - except Exception as e: - logger.warning(f" Impossible de lire le client: {e}") - - nb_lignes_initial = _compter_lignes_document(doc) - logger.info(f" Nombre de lignes actuelles: {nb_lignes_initial}") - - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 3] ANALYSE MODIFICATIONS DEMANDÉES") - logger.info("=" * 80) - - modif_date = "date_devis" in devis_data - modif_date_livraison = "date_livraison" in devis_data - modif_statut = "statut" in devis_data - modif_ref = "reference" in devis_data - modif_lignes = ( - "lignes" in devis_data and devis_data["lignes"] is not None - ) - - logger.info(f" Date devis: {modif_date}") - if modif_date: - logger.info(f" → Valeur: {devis_data['date_devis']}") - - logger.info(f" Date livraison: {modif_date_livraison}") - if modif_date_livraison: - logger.info(f" → Valeur: {devis_data['date_livraison']}") - - logger.info(f" Référence: {modif_ref}") - if modif_ref: - logger.info(f" → Valeur: '{devis_data['reference']}'") - - logger.info(f" Statut: {modif_statut}") - if modif_statut: - logger.info(f" → Valeur: {devis_data['statut']}") - - logger.info(f" Lignes: {modif_lignes}") - if modif_lignes: - logger.info(f" → Nombre: {len(devis_data['lignes'])}") - for i, ligne in enumerate(devis_data["lignes"], 1): - logger.info( - f" → Ligne {i}: {ligne.get('article_code')} (Qté: {ligne.get('quantite')})" - ) - - devis_data_temp = devis_data.copy() - reference_a_modifier = None - statut_a_modifier = None - - if modif_lignes: - logger.info("") - logger.info( - " STRATÉGIE: Report référence/statut APRÈS modification lignes" - ) - - if modif_ref: - reference_a_modifier = devis_data_temp.pop("reference") - logger.info(f" Référence '{reference_a_modifier}' reportée") - modif_ref = False - - if modif_statut: - statut_a_modifier = devis_data_temp.pop("statut") - logger.info(f" Statut {statut_a_modifier} reporté") - modif_statut = False - - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 4] TEST WRITE() BASIQUE") - logger.info("=" * 80) - logger.info("Test sans modification pour vérifier le verrouillage...") - - try: - doc.Write() - logger.info(" Write() basique OK - Document NON verrouillé") - - time.sleep(0.3) - doc.Read() - logger.info(" Read() après Write() OK") - except Exception as e: - logger.error(f" Write() basique ÉCHOUE: {e}") - logger.error(" ABANDON: Document VERROUILLÉ ou problème COM") - raise ValueError(f"Document verrouillé: {e}") - - champs_modifies = [] - - if not modif_lignes and ( - modif_date or modif_date_livraison or modif_statut or modif_ref - ): - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 5A] MODIFICATIONS SIMPLES (sans lignes)") - logger.info("=" * 80) - - if modif_date: - logger.info("") - logger.info(" Modification DATE_DEVIS...") - try: - ancienne_date = getattr(doc, "DO_Date", None) - ancienne_date_str = ( - ancienne_date.strftime("%Y-%m-%d") - if ancienne_date - else "None" - ) - logger.info(f" Actuelle: {ancienne_date_str}") - - 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}") - - doc.DO_Date = pywintypes.Time(nouvelle_date) - logger.info(" doc.DO_Date affecté") - - champs_modifies.append("date_devis") - logger.info( - f" Date devis sera modifiée: {ancienne_date_str} → {nouvelle_date_str}" - ) - except Exception as e: - logger.error(f" Erreur date devis: {e}", exc_info=True) - - if modif_date_livraison: - logger.info("") - logger.info(" Modification DATE_LIVRAISON...") - try: - ancienne_date_livr = getattr(doc, "DO_DateLivr", None) - ancienne_date_livr_str = ( - ancienne_date_livr.strftime("%Y-%m-%d") - if ancienne_date_livr - else "None" - ) - logger.info(f" Actuelle: {ancienne_date_livr_str}") - - if 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}") - - doc.DO_DateLivr = pywintypes.Time(nouvelle_date_livr) - logger.info(" doc.DO_DateLivr affecté") - else: - logger.info(" Cible: Effacement (None)") - doc.DO_DateLivr = None - logger.info(" doc.DO_DateLivr = None") - - champs_modifies.append("date_livraison") - logger.info(" Date livraison sera modifiée") - except Exception as e: - logger.error( - f" Erreur date livraison: {e}", exc_info=True - ) - - if modif_ref: - logger.info("") - logger.info(" Modification RÉFÉRENCE...") - try: - ancienne_ref = getattr(doc, "DO_Ref", "") - logger.info(f" Actuelle: '{ancienne_ref}'") - - nouvelle_ref = ( - str(devis_data_temp["reference"]) - if devis_data_temp["reference"] - else "" - ) - logger.info(f" Cible: '{nouvelle_ref}'") - - doc.DO_Ref = nouvelle_ref - logger.info(" doc.DO_Ref affecté") - - champs_modifies.append("reference") - logger.info( - f" Référence sera modifiée: '{ancienne_ref}' → '{nouvelle_ref}'" - ) - except Exception as e: - logger.error(f" Erreur référence: {e}", exc_info=True) - - if modif_statut: - logger.info("") - logger.info(" Modification STATUT...") - try: - statut_actuel = getattr(doc, "DO_Statut", 0) - logger.info(f" Actuel: {statut_actuel}") - - nouveau_statut = int(devis_data_temp["statut"]) - logger.info(f" Cible: {nouveau_statut}") - - if nouveau_statut in [0, 1, 2, 3]: - doc.DO_Statut = nouveau_statut - logger.info(" doc.DO_Statut affecté") - - champs_modifies.append("statut") - logger.info( - f" Statut sera modifié: {statut_actuel} → {nouveau_statut}" - ) - else: - logger.warning( - f" Statut {nouveau_statut} invalide (doit être 0,1,2 ou 3)" - ) - except Exception as e: - logger.error(f" Erreur statut: {e}", exc_info=True) - - logger.info("") - logger.info(" Write() modifications simples...") - try: - doc.Write() - logger.info(" Write() réussi") - - time.sleep(0.5) - doc.Read() - logger.info(" Read() après Write() OK") - except Exception as e: - logger.error(f" Write() a échoué: {e}", exc_info=True) - raise - - elif modif_lignes: - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 5B] REMPLACEMENT COMPLET DES LIGNES") - logger.info("=" * 80) - - if modif_date: - logger.info(" Modification date devis (avant lignes)...") - try: - 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')}" - ) - except Exception as e: - logger.error(f" Erreur: {e}") - - if modif_date_livraison: - logger.info(" Modification date livraison (avant lignes)...") - try: - if 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: - doc.DO_DateLivr = None - logger.info(" Date livraison effacée") - champs_modifies.append("date_livraison") - except Exception as e: - logger.error(f" Erreur: {e}") - - nouvelles_lignes = devis_data["lignes"] - nb_nouvelles = len(nouvelles_lignes) - - logger.info("") - logger.info( - f" Remplacement: {nb_lignes_initial} lignes → {nb_nouvelles} lignes" - ) - - try: - factory_lignes = doc.FactoryDocumentLigne - except Exception: - factory_lignes = doc.FactoryDocumentVenteLigne - - factory_article = self.cial.FactoryArticle - - if nb_lignes_initial > 0: - logger.info("") - logger.info( - f" Suppression de {nb_lignes_initial} lignes existantes..." - ) - - for idx in range(nb_lignes_initial, 0, -1): - try: - ligne_p = factory_lignes.List(idx) - if ligne_p: - try: - ligne = win32com.client.CastTo( - ligne_p, "IBODocumentLigne3" - ) - except Exception: - ligne = win32com.client.CastTo( - ligne_p, "IBODocumentVenteLigne3" - ) - - ligne.Read() - ligne.Remove() - logger.debug(f" Ligne {idx} supprimée") - except Exception as e: - logger.warning(f" Ligne {idx} non supprimée: {e}") - - logger.info(f" {nb_lignes_initial} lignes supprimées") - - logger.info("") - logger.info(f" Ajout de {nb_nouvelles} nouvelles lignes...") - - for idx, ligne_data in enumerate(nouvelles_lignes, 1): - article_code = ligne_data["article_code"] - quantite = float(ligne_data["quantite"]) - - logger.info("") - logger.info(f" Ligne {idx}/{nb_nouvelles}: {article_code}") - logger.info(f" Quantité: {quantite}") - if ligne_data.get("prix_unitaire_ht"): - logger.info( - f" Prix HT: {ligne_data['prix_unitaire_ht']}€" - ) - if ligne_data.get("remise_pourcentage"): - logger.info( - f" Remise: {ligne_data['remise_pourcentage']}%" - ) - - try: - persist_article = factory_article.ReadReference( - article_code - ) - if not persist_article: - raise ValueError(f"Article {article_code} INTROUVABLE") - - article_obj = win32com.client.CastTo( - persist_article, "IBOArticle3" - ) - article_obj.Read() - logger.info(" Article chargé") - - ligne_persist = factory_lignes.Create() - - try: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentLigne3" - ) - except Exception: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentVenteLigne3" - ) - - try: - ligne_obj.SetDefaultArticleReference( - article_code, quantite - ) - logger.info( - " Article associé via SetDefaultArticleReference" - ) - except Exception: - try: - ligne_obj.SetDefaultArticle(article_obj, quantite) - logger.info( - " Article associé via SetDefaultArticle" - ) - except Exception: - ligne_obj.DL_Design = ligne_data.get( - "designation", "" - ) - ligne_obj.DL_Qte = quantite - logger.info(" Article associé manuellement") - - if ligne_data.get("prix_unitaire_ht"): - ligne_obj.DL_PrixUnitaire = float( - ligne_data["prix_unitaire_ht"] - ) - logger.info(" Prix unitaire défini") - - if ligne_data.get("remise_pourcentage", 0) > 0: - try: - ligne_obj.DL_Remise01REM_Valeur = float( - ligne_data["remise_pourcentage"] - ) - # ligne_obj.DL_Remise01REM_Type = 0 - logger.info(" Remise définie") - except Exception: - logger.debug(" Remise non supportée") - - ligne_obj.Write() - logger.info(f" Ligne {idx} créée avec succès") - - except Exception as e: - logger.error( - f" ERREUR ligne {idx}: {e}", exc_info=True - ) - raise - - logger.info("") - logger.info(f" {nb_nouvelles} lignes créées") - - logger.info("") - logger.info(" Write() après remplacement lignes...") - try: - doc.Write() - logger.info(" Write() réussi") - - time.sleep(0.5) - doc.Read() - logger.info(" Read() après Write() OK") - except Exception as e: - logger.error(f" Write() a échoué: {e}", exc_info=True) - raise - - champs_modifies.append("lignes") - - if reference_a_modifier is not None: - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 6] MODIFICATION RÉFÉRENCE (après lignes)") - logger.info("=" * 80) - - try: - ancienne_ref = getattr(doc, "DO_Ref", "") - nouvelle_ref = ( - str(reference_a_modifier) if reference_a_modifier else "" - ) - - logger.info(f" Actuelle: '{ancienne_ref}'") - logger.info(f" Cible: '{nouvelle_ref}'") - - doc.DO_Ref = nouvelle_ref - logger.info(" doc.DO_Ref affecté") - - doc.Write() - logger.info(" Write()") - - time.sleep(0.5) - doc.Read() - logger.info(" Read()") - - champs_modifies.append("reference") - logger.info( - f" Référence modifiée: '{ancienne_ref}' → '{nouvelle_ref}'" - ) - except Exception as e: - logger.error(f" Erreur référence: {e}", exc_info=True) - - if statut_a_modifier is not None: - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 7] MODIFICATION STATUT (en dernier)") - logger.info("=" * 80) - - try: - statut_actuel = getattr(doc, "DO_Statut", 0) - nouveau_statut = int(statut_a_modifier) - - logger.info(f" Actuel: {statut_actuel}") - logger.info(f" Cible: {nouveau_statut}") - - if nouveau_statut != statut_actuel and nouveau_statut in [ - 0, - 1, - 2, - 3, - ]: - doc.DO_Statut = nouveau_statut - logger.info(" doc.DO_Statut affecté") - - doc.Write() - logger.info(" Write()") - - time.sleep(0.5) - doc.Read() - logger.info(" Read()") - - champs_modifies.append("statut") - logger.info( - f" Statut modifié: {statut_actuel} → {nouveau_statut}" - ) - else: - logger.info( - " Pas de modification (identique ou invalide)" - ) - except Exception as e: - logger.error(f" Erreur statut: {e}", exc_info=True) - - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 8] VALIDATION FINALE") - logger.info("=" * 80) - - try: - doc.Write() - logger.info(" Write() final") - except Exception as e: - logger.warning(f" Write() final: {e}") - - time.sleep(0.5) - doc.Read() - logger.info(" Read() final") - - logger.info("") - _afficher_etat_document(doc, "📸 ÉTAT FINAL") - - logger.info("") - logger.info("=" * 80) - logger.info(" [ÉTAPE 9] EXTRACTION RÉSULTAT") - logger.info("=" * 80) - - resultat = _extraire_infos_devis(doc, numero, champs_modifies) - - logger.info(" Résultat extrait:") - logger.info(f" Numéro: {resultat['numero']}") - logger.info(f" Référence: '{resultat['reference']}'") - logger.info(f" Date devis: {resultat['date_devis']}") - logger.info(f" Date livraison: {resultat['date_livraison']}") - logger.info(f" Statut: {resultat['statut']}") - logger.info(f" Total HT: {resultat['total_ht']}€") - logger.info(f" Total TTC: {resultat['total_ttc']}€") - logger.info(f" Champs modifiés: {resultat['champs_modifies']}") - - logger.info("") - logger.info("=" * 100) - logger.info(f" MODIFICATION DEVIS {numero} TERMINÉE AVEC SUCCÈS ") - logger.info("=" * 100) - - return resultat - - except ValueError as e: - logger.error(f" ERREUR MÉTIER: {e}") - raise - - except Exception as e: - logger.error(f" ERREUR TECHNIQUE: {e}", exc_info=True) - raise RuntimeError(f"Erreur technique Sage: {str(e)}") + def modifier_devis(self, numero: str, devis_data: dict) -> Dict: + """Modifie un devis""" + return modifier_document_vente( + self, numero, devis_data, TypeDocumentVente.DEVIS + ) def _charger_devis(self, numero: str): """Charge un devis depuis Sage.""" @@ -4857,1774 +4297,43 @@ class SageConnector: def creer_commande_enrichi(self, commande_data: dict) -> Dict: """Crée une commande""" - return creer_document_vente(commande_data, TypeDocumentVente.COMMANDE) + return creer_document_vente(self, commande_data, TypeDocumentVente.COMMANDE) - def modifier_commande(self, numero: str, commande_data: Dict) -> Dict: - if not self.cial: - raise RuntimeError("Connexion Sage non établie") - - try: - with self._com_context(), self._lock_com: - logger.info(f" === MODIFICATION COMMANDE {numero} ===") - - logger.info(" Chargement document...") - - factory = self.cial.FactoryDocumentVente - persist = None - - for type_test in [10, 3, settings.SAGE_TYPE_BON_COMMANDE]: - try: - persist_test = factory.ReadPiece(type_test, numero) - if persist_test: - persist = persist_test - logger.info(f" Document trouvé (type={type_test})") - break - except Exception: - continue - - if not persist: - raise ValueError(f" Commande {numero} INTROUVABLE") - - doc = win32com.client.CastTo(persist, "IBODocumentVente3") - doc.Read() - - statut_actuel = getattr(doc, "DO_Statut", 0) - type_reel = getattr(doc, "DO_Type", -1) - - logger.info(f" Type={type_reel}, Statut={statut_actuel}") - - client_code_initial = "" - try: - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_code_initial = getattr(client_obj, "CT_Num", "").strip() - logger.info(f" Client initial: {client_code_initial}") - else: - logger.error(" Objet Client NULL à l'état initial !") - except Exception as e: - logger.error(f" Erreur lecture client initial: {e}") - - if not client_code_initial: - raise ValueError(" Client introuvable dans le document") - - nb_lignes_initial = 0 - try: - factory_lignes = getattr( - doc, "FactoryDocumentLigne", None - ) or getattr(doc, "FactoryDocumentVenteLigne", None) - index = 1 - while index <= 100: - try: - ligne_p = factory_lignes.List(index) - if ligne_p is None: - break - nb_lignes_initial += 1 - index += 1 - except Exception: - break - - logger.info(f" Lignes initiales: {nb_lignes_initial}") - except Exception as e: - logger.warning(f" Erreur comptage lignes: {e}") - - champs_modifies = [] - - modif_date = "date_commande" in commande_data - modif_date_livraison = "date_livraison" in commande_data - modif_statut = "statut" in commande_data - modif_ref = "reference" in commande_data - modif_lignes = ( - "lignes" in commande_data and commande_data["lignes"] is not None - ) - - logger.info("Modifications demandées:") - logger.info(f" Date commande: {modif_date}") - logger.info(f" Date livraison: {modif_date_livraison}") - logger.info(f" Statut: {modif_statut}") - logger.info(f" Référence: {modif_ref}") - logger.info(f" Lignes: {modif_lignes}") - - commande_data_temp = commande_data.copy() - reference_a_modifier = None - statut_a_modifier = None - - if modif_lignes: - if modif_ref: - reference_a_modifier = commande_data_temp.pop("reference") - logger.info( - " Modification de la référence reportée après les lignes" - ) - modif_ref = False - - if modif_statut: - statut_a_modifier = commande_data_temp.pop("statut") - logger.info(" Modification du statut reportée après les lignes") - modif_statut = False - - logger.info(" Test Write() basique (sans modification)...") - - try: - doc.Write() - logger.info(" Write() basique OK") - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - if client_apres == client_code_initial: - logger.info(f" Client préservé: {client_apres}") - else: - logger.error( - f" Client a changé: {client_code_initial} → {client_apres}" - ) - else: - logger.error(" Client devenu NULL après Write() basique") - - except Exception as e: - logger.error(f" Write() basique ÉCHOUE: {e}") - logger.error(" ABANDON: Le document est VERROUILLÉ") - raise ValueError( - f"Document verrouillé, impossible de modifier: {e}" - ) - - if not modif_lignes and ( - modif_date or modif_date_livraison or modif_statut or modif_ref - ): - logger.info(" Modifications simples (sans lignes)...") - - if modif_date: - logger.info(" Modification date commande...") - doc.DO_Date = pywintypes.Time( - normaliser_date(commande_data_temp.get("date_commande")) - ) - champs_modifies.append("date") - - if modif_date_livraison: - logger.info(" Modification date livraison...") - doc.DO_DateLivr = pywintypes.Time( - normaliser_date(commande_data_temp["date_livraison"]) - ) - logger.info( - f" Date livraison: {commande_data_temp['date_livraison']}" - ) - champs_modifies.append("date_livraison") - - if modif_statut: - logger.info(" Modification statut...") - nouveau_statut = commande_data_temp["statut"] - doc.DO_Statut = nouveau_statut - champs_modifies.append("statut") - logger.info(f" Statut défini: {nouveau_statut}") - - if modif_ref: - logger.info(" Modification référence...") - try: - doc.DO_Ref = commande_data_temp["reference"] - champs_modifies.append("reference") - logger.info( - f" Référence définie: {commande_data_temp['reference']}" - ) - except Exception as e: - logger.warning(f" Référence non définie: {e}") - - logger.info(" Write() sans réassociation client...") - try: - doc.Write() - logger.info(" Write() réussi") - - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - if client_apres == client_code_initial: - logger.info(f" Client préservé: {client_apres}") - else: - logger.error( - f" Client perdu: {client_code_initial} → {client_apres}" - ) - - except Exception as e: - error_msg = str(e) - try: - sage_error = self.cial.CptaApplication.LastError - if sage_error: - error_msg = f"{sage_error.Description} (Code: {sage_error.Number})" - except Exception: - pass - - logger.error(f" Write() échoue: {error_msg}") - raise ValueError(f"Sage refuse: {error_msg}") - - elif modif_lignes: - logger.info(" REMPLACEMENT COMPLET DES LIGNES...") - - if modif_date: - doc.DO_Date = pywintypes.Time( - normaliser_date(commande_data_temp.get("date_commande")) - ) - champs_modifies.append("date") - logger.info(" Date commande modifiée") - - if modif_date_livraison: - doc.DO_DateLivr = pywintypes.Time( - normaliser_date(commande_data_temp["date_livraison"]) - ) - logger.info(" Date livraison modifiée") - champs_modifies.append("date_livraison") - - nouvelles_lignes = commande_data["lignes"] - nb_nouvelles = len(nouvelles_lignes) - - logger.info( - f" {nb_lignes_initial} lignes existantes → {nb_nouvelles} nouvelles lignes" - ) - - try: - factory_lignes = doc.FactoryDocumentLigne - except Exception: - factory_lignes = doc.FactoryDocumentVenteLigne - - factory_article = self.cial.FactoryArticle - - if nb_lignes_initial > 0: - logger.info( - f" Suppression de {nb_lignes_initial} lignes existantes..." - ) - - for idx in range(nb_lignes_initial, 0, -1): - try: - ligne_p = factory_lignes.List(idx) - if ligne_p: - ligne = win32com.client.CastTo( - ligne_p, "IBODocumentLigne3" - ) - ligne.Read() - - ligne.Remove() - logger.debug(f" Ligne {idx} supprimée") - except Exception as e: - logger.warning( - f" Impossible de supprimer ligne {idx}: {e}" - ) - - logger.info(" Toutes les lignes existantes supprimées") - - logger.info(f" Ajout de {nb_nouvelles} nouvelles lignes...") - - for idx, ligne_data in enumerate(nouvelles_lignes, 1): - logger.info( - f" --- Ligne {idx}/{nb_nouvelles}: {ligne_data['article_code']} ---" - ) - - persist_article = factory_article.ReadReference( - ligne_data["article_code"] - ) - if not persist_article: - raise ValueError( - f"Article {ligne_data['article_code']} introuvable" - ) - - article_obj = win32com.client.CastTo( - persist_article, "IBOArticle3" - ) - article_obj.Read() - - ligne_persist = factory_lignes.Create() - - try: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentLigne3" - ) - except Exception: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentVenteLigne3" - ) - - quantite = float(ligne_data["quantite"]) - - try: - ligne_obj.SetDefaultArticleReference( - ligne_data["article_code"], quantite - ) - except Exception: - try: - ligne_obj.SetDefaultArticle(article_obj, quantite) - except Exception: - ligne_obj.DL_Design = ligne_data.get("designation", "") - ligne_obj.DL_Qte = quantite - - if ligne_data.get("prix_unitaire_ht"): - ligne_obj.DL_PrixUnitaire = float( - ligne_data["prix_unitaire_ht"] - ) - - if ligne_data.get("remise_pourcentage", 0) > 0: - try: - ligne_obj.DL_Remise01REM_Valeur = float( - ligne_data["remise_pourcentage"] - ) - # ligne_obj.DL_Remise01REM_Type = 0 - except Exception: - pass - - ligne_obj.Write() - logger.info(f" Ligne {idx} ajoutée") - - logger.info(f" {nb_nouvelles} nouvelles lignes ajoutées") - - logger.info(" Write() document après remplacement lignes...") - doc.Write() - logger.info(" Document écrit") - - import time - - time.sleep(0.5) - - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - logger.info(f" Client après remplacement: {client_apres}") - else: - logger.error(" Client NULL après remplacement") - - champs_modifies.append("lignes") - - if reference_a_modifier is not None: - try: - ancienne_reference = getattr(doc, "DO_Ref", "") - nouvelle_reference = ( - str(reference_a_modifier) if reference_a_modifier else "" - ) - - doc.DO_Ref = nouvelle_reference - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("reference") - logger.info( - f" Référence modifiée: '{ancienne_reference}' → '{nouvelle_reference}'" - ) - except Exception as e: - logger.warning(f"Impossible de modifier la référence: {e}") - - if statut_a_modifier is not None: - try: - statut_actuel = getattr(doc, "DO_Statut", 0) - nouveau_statut = int(statut_a_modifier) - - if nouveau_statut != statut_actuel: - doc.DO_Statut = nouveau_statut - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("statut") - logger.info( - f" Statut modifié: {statut_actuel} → {nouveau_statut}" - ) - except Exception as e: - logger.warning(f"Impossible de modifier le statut: {e}") - - logger.info(" Relecture finale...") - - import time - - time.sleep(0.5) - - doc.Read() - - client_obj_final = getattr(doc, "Client", None) - if client_obj_final: - client_obj_final.Read() - client_final = getattr(client_obj_final, "CT_Num", "") - else: - client_final = "" - - total_ht = float(getattr(doc, "DO_TotalHT", 0.0)) - total_ttc = float(getattr(doc, "DO_TotalTTC", 0.0)) - reference_finale = getattr(doc, "DO_Ref", "") - - date_livraison_final = None - - try: - date_livr = getattr(doc, "DO_DateLivr", None) - if date_livr: - date_livraison_final = date_livr.strftime("%Y-%m-%d") - except Exception: - pass - - logger.info(f" SUCCÈS: {numero} modifiée ") - logger.info(f" Totaux: {total_ht}€ HT / {total_ttc}€ TTC") - logger.info(f" Client final: {client_final}") - logger.info(f" Référence: {reference_finale}") - if date_livraison_final: - logger.info(f" Date livraison: {date_livraison_final}") - logger.info(f" Champs modifiés: {champs_modifies}") - - return { - "numero": numero, - "total_ht": total_ht, - "total_ttc": total_ttc, - "reference": reference_finale, - "date_livraison": date_livraison_final, - "champs_modifies": champs_modifies, - "statut": getattr(doc, "DO_Statut", 0), - "client_code": client_final, - } - - except ValueError as e: - logger.error(f" ERREUR MÉTIER: {e}") - raise - - except Exception as e: - logger.error(f" ERREUR TECHNIQUE: {e}", exc_info=True) - - error_message = str(e) - if self.cial: - try: - err = self.cial.CptaApplication.LastError - if err: - error_message = ( - f"Erreur Sage: {err.Description} (Code: {err.Number})" - ) - except Exception: - pass - - raise RuntimeError(f"Erreur Sage: {error_message}") + def modifier_commande(self, numero: str, commande_data: dict) -> Dict: + """Modifie une commande""" + return modifier_document_vente( + self, numero, commande_data, TypeDocumentVente.COMMANDE + ) def creer_livraison_enrichi(self, livraison_data: dict) -> Dict: """Crée un bon de livraison""" - return creer_document_vente(livraison_data, TypeDocumentVente.LIVRAISON) + return creer_document_vente(self, livraison_data, TypeDocumentVente.LIVRAISON) - def modifier_livraison(self, numero: str, livraison_data: Dict) -> Dict: - if not self.cial: - raise RuntimeError("Connexion Sage non établie") - - try: - with self._com_context(), self._lock_com: - logger.info(f" === MODIFICATION LIVRAISON {numero} ===") - - logger.info(" Chargement document...") - - factory = self.cial.FactoryDocumentVente - persist = None - - for type_test in [30, settings.SAGE_TYPE_BON_LIVRAISON]: - try: - persist_test = factory.ReadPiece(type_test, numero) - if persist_test: - persist = persist_test - logger.info(f" Document trouvé (type={type_test})") - break - except Exception: - continue - - if not persist: - raise ValueError(f" Livraison {numero} INTROUVABLE") - - doc = win32com.client.CastTo(persist, "IBODocumentVente3") - doc.Read() - - statut_actuel = getattr(doc, "DO_Statut", 0) - - logger.info(f" Statut={statut_actuel}") - - if statut_actuel == 5: - raise ValueError(f"La livraison {numero} a déjà été transformée") - - if statut_actuel == 6: - raise ValueError(f"La livraison {numero} est annulée") - - nb_lignes_initial = 0 - try: - factory_lignes = getattr( - doc, "FactoryDocumentLigne", None - ) or getattr(doc, "FactoryDocumentVenteLigne", None) - index = 1 - while index <= 100: - try: - ligne_p = factory_lignes.List(index) - if ligne_p is None: - break - nb_lignes_initial += 1 - index += 1 - except Exception: - break - - logger.info(f" Lignes initiales: {nb_lignes_initial}") - except Exception as e: - logger.warning(f" Erreur comptage lignes: {e}") - - champs_modifies = [] - - modif_date = "date_livraison" in livraison_data - modif_date_livraison_prevue = "date_livraison_prevue" in livraison_data - modif_statut = "statut" in livraison_data - modif_ref = "reference" in livraison_data - modif_lignes = ( - "lignes" in livraison_data and livraison_data["lignes"] is not None - ) - - logger.info("Modifications demandées:") - logger.info(f" Date livraison: {modif_date}") - logger.info(f" Date livraison prévue: {modif_date_livraison_prevue}") - logger.info(f" Statut: {modif_statut}") - logger.info(f" Référence: {modif_ref}") - logger.info(f" Lignes: {modif_lignes}") - - livraison_data_temp = livraison_data.copy() - reference_a_modifier = None - statut_a_modifier = None - - if modif_lignes: - if modif_ref: - reference_a_modifier = livraison_data_temp.pop("reference") - logger.info( - " Modification de la référence reportée après les lignes" - ) - modif_ref = False - - if modif_statut: - statut_a_modifier = livraison_data_temp.pop("statut") - logger.info(" Modification du statut reportée après les lignes") - modif_statut = False - - if not modif_lignes and ( - modif_date - or modif_date_livraison_prevue - or modif_statut - or modif_ref - ): - logger.info(" Modifications simples (sans lignes)...") - - if modif_date: - logger.info(" Modification date livraison...") - doc.DO_Date = pywintypes.Time( - normaliser_date(livraison_data_temp.get("date_livraison")) - ) - champs_modifies.append("date") - - if modif_date_livraison_prevue: - logger.info(" Modification date livraison prévue...") - doc.DO_DateLivr = pywintypes.Time( - normaliser_date( - livraison_data_temp["date_livraison_prevue"] - ) - ) - logger.info( - f" Date livraison prévue: {livraison_data_temp['date_livraison_prevue']}" - ) - champs_modifies.append("date_livraison_prevue") - - if modif_statut: - logger.info(" Modification statut...") - nouveau_statut = livraison_data_temp["statut"] - doc.DO_Statut = nouveau_statut - champs_modifies.append("statut") - logger.info(f" Statut défini: {nouveau_statut}") - - if modif_ref: - logger.info(" Modification référence...") - try: - doc.DO_Ref = livraison_data_temp["reference"] - champs_modifies.append("reference") - logger.info( - f" Référence définie: {livraison_data_temp['reference']}" - ) - except Exception as e: - logger.warning(f" Référence non définie: {e}") - - logger.info(" Write()...") - doc.Write() - logger.info(" Write() réussi") - - elif modif_lignes: - logger.info(" REMPLACEMENT COMPLET DES LIGNES...") - - if modif_date: - doc.DO_Date = pywintypes.Time( - normaliser_date(livraison_data_temp.get("date_livraison")) - ) - champs_modifies.append("date") - logger.info(" Date livraison modifiée") - - if modif_date_livraison_prevue: - doc.DO_DateLivr = pywintypes.Time( - normaliser_date( - livraison_data_temp["date_livraison_prevue"] - ) - ) - logger.info(" Date livraison prévue modifiée") - champs_modifies.append("date_livraison_prevue") - - nouvelles_lignes = livraison_data["lignes"] - nb_nouvelles = len(nouvelles_lignes) - - logger.info( - f" {nb_lignes_initial} lignes existantes → {nb_nouvelles} nouvelles" - ) - - try: - factory_lignes = doc.FactoryDocumentLigne - except Exception: - factory_lignes = doc.FactoryDocumentVenteLigne - - factory_article = self.cial.FactoryArticle - - if nb_lignes_initial > 0: - logger.info(f" Suppression de {nb_lignes_initial} lignes...") - - for idx in range(nb_lignes_initial, 0, -1): - try: - ligne_p = factory_lignes.List(idx) - if ligne_p: - ligne = win32com.client.CastTo( - ligne_p, "IBODocumentLigne3" - ) - ligne.Read() - ligne.Remove() - logger.debug(f" Ligne {idx} supprimée") - except Exception as e: - logger.warning( - f" Erreur suppression ligne {idx}: {e}" - ) - - logger.info(" Toutes les lignes supprimées") - - logger.info(f" Ajout de {nb_nouvelles} nouvelles lignes...") - - for idx, ligne_data in enumerate(nouvelles_lignes, 1): - logger.info( - f" --- Ligne {idx}/{nb_nouvelles}: {ligne_data['article_code']} ---" - ) - - persist_article = factory_article.ReadReference( - ligne_data["article_code"] - ) - if not persist_article: - raise ValueError( - f"Article {ligne_data['article_code']} introuvable" - ) - - article_obj = win32com.client.CastTo( - persist_article, "IBOArticle3" - ) - article_obj.Read() - - ligne_persist = factory_lignes.Create() - - try: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentLigne3" - ) - except Exception: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentVenteLigne3" - ) - - quantite = float(ligne_data["quantite"]) - - try: - ligne_obj.SetDefaultArticleReference( - ligne_data["article_code"], quantite - ) - except Exception: - try: - ligne_obj.SetDefaultArticle(article_obj, quantite) - except Exception: - ligne_obj.DL_Design = ligne_data.get("designation", "") - ligne_obj.DL_Qte = quantite - - if ligne_data.get("prix_unitaire_ht"): - ligne_obj.DL_PrixUnitaire = float( - ligne_data["prix_unitaire_ht"] - ) - - if ligne_data.get("remise_pourcentage", 0) > 0: - try: - ligne_obj.DL_Remise01REM_Valeur = float( - ligne_data["remise_pourcentage"] - ) - # ligne_obj.DL_Remise01REM_Type = 0 - except Exception: - pass - - ligne_obj.Write() - logger.info(f" Ligne {idx} ajoutée") - - logger.info(f" {nb_nouvelles} nouvelles lignes ajoutées") - - logger.info(" Write() document après remplacement lignes...") - doc.Write() - logger.info(" Document écrit") - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("lignes") - - if reference_a_modifier is not None: - try: - ancienne_reference = getattr(doc, "DO_Ref", "") - nouvelle_reference = ( - str(reference_a_modifier) if reference_a_modifier else "" - ) - - logger.info( - f" Modification référence: '{ancienne_reference}' → '{nouvelle_reference}'" - ) - - doc.DO_Ref = nouvelle_reference - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("reference") - logger.info(" Référence modifiée avec succès") - except Exception as e: - logger.warning(f"Impossible de modifier la référence: {e}") - - if statut_a_modifier is not None: - try: - statut_actuel = getattr(doc, "DO_Statut", 0) - nouveau_statut = int(statut_a_modifier) - - if nouveau_statut != statut_actuel: - logger.info( - f" Modification statut: {statut_actuel} → {nouveau_statut}" - ) - - doc.DO_Statut = nouveau_statut - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("statut") - logger.info(" Statut modifié avec succès") - except Exception as e: - logger.warning(f"Impossible de modifier le statut: {e}") - - logger.info(" Relecture finale...") - - import time - - time.sleep(0.5) - - doc.Read() - - total_ht = float(getattr(doc, "DO_TotalHT", 0.0)) - total_ttc = float(getattr(doc, "DO_TotalTTC", 0.0)) - reference_finale = getattr(doc, "DO_Ref", "") - statut_final = getattr(doc, "DO_Statut", 0) - - date_livraison_prevue_final = None - - try: - date_livr = getattr(doc, "DO_DateLivr", None) - if date_livr: - date_livraison_prevue_final = date_livr.strftime("%Y-%m-%d") - except Exception: - pass - - logger.info(f" SUCCÈS: {numero} modifiée ") - logger.info(f" Totaux: {total_ht}€ HT / {total_ttc}€ TTC") - logger.info(f" Référence: {reference_finale}") - logger.info(f" Statut: {statut_final}") - - if date_livraison_prevue_final: - logger.info( - f" Date livraison prévue: {date_livraison_prevue_final}" - ) - logger.info(f" Champs modifiés: {champs_modifies}") - - return { - "numero": numero, - "total_ht": total_ht, - "total_ttc": total_ttc, - "reference": reference_finale, - "date_livraison_prevue": date_livraison_prevue_final, - "champs_modifies": champs_modifies, - "statut": statut_final, - } - - except ValueError as e: - logger.error(f" ERREUR MÉTIER: {e}") - raise - - except Exception as e: - logger.error(f" ERREUR TECHNIQUE: {e}", exc_info=True) - - error_message = str(e) - if self.cial: - try: - err = self.cial.CptaApplication.LastError - if err: - error_message = ( - f"Erreur Sage: {err.Description} (Code: {err.Number})" - ) - except Exception: - pass - - raise RuntimeError(f"Erreur Sage: {error_message}") + def modifier_livraison(self, numero: str, livraison_data: dict) -> Dict: + """Modifie un bon de livraison""" + return modifier_document_vente( + self, numero, livraison_data, TypeDocumentVente.LIVRAISON + ) def creer_avoir_enrichi(self, avoir_data: dict) -> Dict: """Crée un avoir""" - return creer_document_vente(avoir_data, TypeDocumentVente.AVOIR) + return creer_document_vente(self, avoir_data, TypeDocumentVente.AVOIR) - def modifier_avoir(self, numero: str, avoir_data: Dict) -> Dict: - if not self.cial: - raise RuntimeError("Connexion Sage non établie") - - try: - with self._com_context(), self._lock_com: - logger.info(f" === MODIFICATION AVOIR {numero} ===") - - logger.info(" Chargement document...") - - factory = self.cial.FactoryDocumentVente - persist = None - - for type_test in [50, settings.SAGE_TYPE_BON_AVOIR]: - try: - persist_test = factory.ReadPiece(type_test, numero) - if persist_test: - persist = persist_test - logger.info(f" Document trouvé (type={type_test})") - break - except Exception: - continue - - if not persist: - raise ValueError(f" Avoir {numero} INTROUVABLE") - - doc = win32com.client.CastTo(persist, "IBODocumentVente3") - doc.Read() - - statut_actuel = getattr(doc, "DO_Statut", 0) - type_reel = getattr(doc, "DO_Type", -1) - - logger.info(f" Type={type_reel}, Statut={statut_actuel}") - - if statut_actuel == 5: - raise ValueError(f"L'avoir {numero} a déjà été transformé") - - if statut_actuel == 6: - raise ValueError(f"L'avoir {numero} est annulé") - - client_code_initial = "" - try: - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_code_initial = getattr(client_obj, "CT_Num", "").strip() - logger.info(f" Client initial: {client_code_initial}") - else: - logger.error(" Objet Client NULL à l'état initial !") - except Exception as e: - logger.error(f" Erreur lecture client initial: {e}") - - if not client_code_initial: - raise ValueError(" Client introuvable dans le document") - - nb_lignes_initial = 0 - try: - factory_lignes = getattr( - doc, "FactoryDocumentLigne", None - ) or getattr(doc, "FactoryDocumentVenteLigne", None) - index = 1 - while index <= 100: - try: - ligne_p = factory_lignes.List(index) - if ligne_p is None: - break - nb_lignes_initial += 1 - index += 1 - except Exception: - break - - logger.info(f" Lignes initiales: {nb_lignes_initial}") - except Exception as e: - logger.warning(f" Erreur comptage lignes: {e}") - - champs_modifies = [] - - modif_date = "date_avoir" in avoir_data - modif_date_livraison = "date_livraison" in avoir_data - modif_statut = "statut" in avoir_data - modif_ref = "reference" in avoir_data - modif_lignes = ( - "lignes" in avoir_data and avoir_data["lignes"] is not None - ) - - logger.info("Modifications demandées:") - logger.info(f" Date avoir: {modif_date}") - logger.info(f" Date livraison: {modif_date_livraison}") - logger.info(f" Statut: {modif_statut}") - logger.info(f" Référence: {modif_ref}") - logger.info(f" Lignes: {modif_lignes}") - - avoir_data_temp = avoir_data.copy() - reference_a_modifier = None - statut_a_modifier = None - - if modif_lignes: - if modif_ref: - reference_a_modifier = avoir_data_temp.pop("reference") - logger.info( - " Modification de la référence reportée après les lignes" - ) - modif_ref = False - - if modif_statut: - statut_a_modifier = avoir_data_temp.pop("statut") - logger.info(" Modification du statut reportée après les lignes") - modif_statut = False - - logger.info(" Test Write() basique (sans modification)...") - - try: - doc.Write() - logger.info(" Write() basique OK") - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - if client_apres == client_code_initial: - logger.info(f" Client préservé: {client_apres}") - else: - logger.error( - f" Client a changé: {client_code_initial} → {client_apres}" - ) - else: - logger.error(" Client devenu NULL après Write() basique") - - except Exception as e: - logger.error(f" Write() basique ÉCHOUE: {e}") - logger.error(" ABANDON: Le document est VERROUILLÉ") - raise ValueError( - f"Document verrouillé, impossible de modifier: {e}" - ) - - if not modif_lignes and ( - modif_date or modif_date_livraison or modif_statut or modif_ref - ): - logger.info(" Modifications simples (sans lignes)...") - - if modif_date: - logger.info(" Modification date avoir...") - doc.DO_Date = pywintypes.Time( - 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( - normaliser_date(avoir_data_temp["date_livraison"]) - ) - logger.info( - f" Date livraison: {avoir_data_temp['date_livraison']}" - ) - champs_modifies.append("date_livraison") - - if modif_statut: - logger.info(" Modification statut...") - nouveau_statut = avoir_data_temp["statut"] - doc.DO_Statut = nouveau_statut - champs_modifies.append("statut") - logger.info(f" Statut défini: {nouveau_statut}") - - if modif_ref: - logger.info(" Modification référence...") - try: - doc.DO_Ref = avoir_data_temp["reference"] - champs_modifies.append("reference") - logger.info( - f" Référence définie: {avoir_data_temp['reference']}" - ) - except Exception as e: - logger.warning(f" Référence non définie: {e}") - - logger.info(" Write() sans réassociation client...") - try: - doc.Write() - logger.info(" Write() réussi") - - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - if client_apres == client_code_initial: - logger.info(f" Client préservé: {client_apres}") - else: - logger.error( - f" Client perdu: {client_code_initial} → {client_apres}" - ) - - except Exception as e: - error_msg = str(e) - try: - sage_error = self.cial.CptaApplication.LastError - if sage_error: - error_msg = f"{sage_error.Description} (Code: {sage_error.Number})" - except Exception: - pass - - logger.error(f" Write() échoue: {error_msg}") - raise ValueError(f"Sage refuse: {error_msg}") - - elif modif_lignes: - logger.info(" REMPLACEMENT COMPLET DES LIGNES...") - - if modif_date: - doc.DO_Date = pywintypes.Time( - 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( - normaliser_date(avoir_data_temp["date_livraison"]) - ) - logger.info(" Date livraison modifiée") - champs_modifies.append("date_livraison") - - nouvelles_lignes = avoir_data["lignes"] - nb_nouvelles = len(nouvelles_lignes) - - logger.info( - f" {nb_lignes_initial} lignes existantes → {nb_nouvelles} nouvelles lignes" - ) - - try: - factory_lignes = doc.FactoryDocumentLigne - except Exception: - factory_lignes = doc.FactoryDocumentVenteLigne - - factory_article = self.cial.FactoryArticle - - if nb_lignes_initial > 0: - logger.info( - f" Suppression de {nb_lignes_initial} lignes existantes..." - ) - - for idx in range(nb_lignes_initial, 0, -1): - try: - ligne_p = factory_lignes.List(idx) - if ligne_p: - ligne = win32com.client.CastTo( - ligne_p, "IBODocumentLigne3" - ) - ligne.Read() - - ligne.Remove() - logger.debug(f" Ligne {idx} supprimée") - except Exception as e: - logger.warning( - f" Impossible de supprimer ligne {idx}: {e}" - ) - - logger.info(" Toutes les lignes existantes supprimées") - - logger.info(f" Ajout de {nb_nouvelles} nouvelles lignes...") - - for idx, ligne_data in enumerate(nouvelles_lignes, 1): - logger.info( - f" --- Ligne {idx}/{nb_nouvelles}: {ligne_data['article_code']} ---" - ) - - persist_article = factory_article.ReadReference( - ligne_data["article_code"] - ) - if not persist_article: - raise ValueError( - f"Article {ligne_data['article_code']} introuvable" - ) - - article_obj = win32com.client.CastTo( - persist_article, "IBOArticle3" - ) - article_obj.Read() - - ligne_persist = factory_lignes.Create() - - try: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentLigne3" - ) - except Exception: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentVenteLigne3" - ) - - quantite = float(ligne_data["quantite"]) - - try: - ligne_obj.SetDefaultArticleReference( - ligne_data["article_code"], quantite - ) - except Exception: - try: - ligne_obj.SetDefaultArticle(article_obj, quantite) - except Exception: - ligne_obj.DL_Design = ligne_data.get("designation", "") - ligne_obj.DL_Qte = quantite - - if ligne_data.get("prix_unitaire_ht"): - ligne_obj.DL_PrixUnitaire = float( - ligne_data["prix_unitaire_ht"] - ) - - if ligne_data.get("remise_pourcentage", 0) > 0: - try: - ligne_obj.DL_Remise01REM_Valeur = float( - ligne_data["remise_pourcentage"] - ) - # ligne_obj.DL_Remise01REM_Type = 0 - except Exception: - pass - - ligne_obj.Write() - logger.info(f" Ligne {idx} ajoutée") - - logger.info(f" {nb_nouvelles} nouvelles lignes ajoutées") - - logger.info(" Write() document après remplacement lignes...") - doc.Write() - logger.info(" Document écrit") - - import time - - time.sleep(0.5) - - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - logger.info(f" Client après remplacement: {client_apres}") - else: - logger.error(" Client NULL après remplacement") - - champs_modifies.append("lignes") - - if reference_a_modifier is not None: - try: - ancienne_reference = getattr(doc, "DO_Ref", "") - nouvelle_reference = ( - str(reference_a_modifier) if reference_a_modifier else "" - ) - - logger.info( - f" Modification référence: '{ancienne_reference}' → '{nouvelle_reference}'" - ) - - doc.DO_Ref = nouvelle_reference - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("reference") - logger.info(" Référence modifiée avec succès") - except Exception as e: - logger.warning(f"Impossible de modifier la référence: {e}") - - if statut_a_modifier is not None: - try: - statut_actuel = getattr(doc, "DO_Statut", 0) - nouveau_statut = int(statut_a_modifier) - - if nouveau_statut != statut_actuel: - logger.info( - f" Modification statut: {statut_actuel} → {nouveau_statut}" - ) - - doc.DO_Statut = nouveau_statut - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("statut") - logger.info(" Statut modifié avec succès") - except Exception as e: - logger.warning(f"Impossible de modifier le statut: {e}") - - logger.info(" Relecture finale...") - - import time - - time.sleep(0.5) - - doc.Read() - - client_obj_final = getattr(doc, "Client", None) - if client_obj_final: - client_obj_final.Read() - client_final = getattr(client_obj_final, "CT_Num", "") - else: - client_final = "" - - total_ht = float(getattr(doc, "DO_TotalHT", 0.0)) - total_ttc = float(getattr(doc, "DO_TotalTTC", 0.0)) - reference_finale = getattr(doc, "DO_Ref", "") - statut_final = getattr(doc, "DO_Statut", 0) - - date_livraison_final = None - - try: - date_livr = getattr(doc, "DO_DateLivr", None) - if date_livr: - date_livraison_final = date_livr.strftime("%Y-%m-%d") - except Exception: - pass - - logger.info(f" SUCCÈS: {numero} modifié ") - logger.info(f" Totaux: {total_ht}€ HT / {total_ttc}€ TTC") - logger.info(f" Client final: {client_final}") - logger.info(f" Référence: {reference_finale}") - logger.info(f" Statut: {statut_final}") - - if date_livraison_final: - logger.info(f" Date livraison: {date_livraison_final}") - logger.info(f" Champs modifiés: {champs_modifies}") - - return { - "numero": numero, - "total_ht": total_ht, - "total_ttc": total_ttc, - "reference": reference_finale, - "date_livraison": date_livraison_final, - "champs_modifies": champs_modifies, - "statut": statut_final, - "client_code": client_final, - } - - except ValueError as e: - logger.error(f" ERREUR MÉTIER: {e}") - raise - - except Exception as e: - logger.error(f" ERREUR TECHNIQUE: {e}", exc_info=True) - - error_message = str(e) - if self.cial: - try: - err = self.cial.CptaApplication.LastError - if err: - error_message = ( - f"Erreur Sage: {err.Description} (Code: {err.Number})" - ) - except Exception: - pass - - raise RuntimeError(f"Erreur Sage: {error_message}") + def modifier_avoir(self, numero: str, avoir_data: dict) -> Dict: + """Modifie un avoir""" + return modifier_document_vente( + self, numero, avoir_data, TypeDocumentVente.AVOIR + ) def creer_facture_enrichi(self, facture_data: dict) -> Dict: """Crée une facture""" - return creer_document_vente(facture_data, TypeDocumentVente.FACTURE) + return creer_document_vente(self, facture_data, TypeDocumentVente.FACTURE) - def modifier_facture(self, numero: str, facture_data: Dict) -> Dict: - if not self.cial: - raise RuntimeError("Connexion Sage non établie") - - try: - with self._com_context(), self._lock_com: - logger.info(f" === MODIFICATION FACTURE {numero} ===") - - logger.info(" Chargement document...") - - factory = self.cial.FactoryDocumentVente - persist = None - - for type_test in [60, 5, settings.SAGE_TYPE_FACTURE]: - try: - persist_test = factory.ReadPiece(type_test, numero) - if persist_test: - persist = persist_test - logger.info(f" Document trouvé (type={type_test})") - break - except Exception: - continue - - if not persist: - raise ValueError(f" Facture {numero} INTROUVABLE") - - doc = win32com.client.CastTo(persist, "IBODocumentVente3") - doc.Read() - - statut_actuel = getattr(doc, "DO_Statut", 0) - type_reel = getattr(doc, "DO_Type", -1) - - logger.info(f" Type={type_reel}, Statut={statut_actuel}") - - if statut_actuel == 5: - raise ValueError(f"La facture {numero} a déjà été transformée") - - if statut_actuel == 6: - raise ValueError(f"La facture {numero} est annulée") - - client_code_initial = "" - try: - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_code_initial = getattr(client_obj, "CT_Num", "").strip() - logger.info(f" Client initial: {client_code_initial}") - else: - logger.error(" Objet Client NULL à l'état initial !") - except Exception as e: - logger.error(f" Erreur lecture client initial: {e}") - - if not client_code_initial: - raise ValueError(" Client introuvable dans le document") - - nb_lignes_initial = 0 - try: - factory_lignes = getattr( - doc, "FactoryDocumentLigne", None - ) or getattr(doc, "FactoryDocumentVenteLigne", None) - index = 1 - while index <= 100: - try: - ligne_p = factory_lignes.List(index) - if ligne_p is None: - break - nb_lignes_initial += 1 - index += 1 - except Exception: - break - - logger.info(f" Lignes initiales: {nb_lignes_initial}") - except Exception as e: - logger.warning(f" Erreur comptage lignes: {e}") - - champs_modifies = [] - - modif_date = "date_facture" in facture_data - modif_date_livraison = "date_livraison" in facture_data - modif_statut = "statut" in facture_data - modif_ref = "reference" in facture_data - modif_lignes = ( - "lignes" in facture_data and facture_data["lignes"] is not None - ) - - logger.info("Modifications demandées:") - logger.info(f" Date facture: {modif_date}") - logger.info(f" Date livraison: {modif_date_livraison}") - logger.info(f" Statut: {modif_statut}") - logger.info(f" Référence: {modif_ref}") - logger.info(f" Lignes: {modif_lignes}") - - facture_data_temp = facture_data.copy() - reference_a_modifier = None - statut_a_modifier = None - - if modif_lignes: - if modif_ref: - reference_a_modifier = facture_data_temp.pop("reference") - logger.info( - " Modification de la référence reportée après les lignes" - ) - modif_ref = False - - if modif_statut: - statut_a_modifier = facture_data_temp.pop("statut") - logger.info(" Modification du statut reportée après les lignes") - modif_statut = False - - logger.info(" Test Write() basique (sans modification)...") - - try: - doc.Write() - logger.info(" Write() basique OK") - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - if client_apres == client_code_initial: - logger.info(f" Client préservé: {client_apres}") - else: - logger.error( - f" Client a changé: {client_code_initial} → {client_apres}" - ) - else: - logger.error(" Client devenu NULL après Write() basique") - - except Exception as e: - logger.error(f" Write() basique ÉCHOUE: {e}") - logger.error(" ABANDON: Le document est VERROUILLÉ") - raise ValueError( - f"Document verrouillé, impossible de modifier: {e}" - ) - - if not modif_lignes and ( - modif_date or modif_date_livraison or modif_statut or modif_ref - ): - logger.info(" Modifications simples (sans lignes)...") - - if modif_date: - logger.info(" Modification date facture...") - doc.DO_Date = pywintypes.Time( - 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( - normaliser_date(facture_data_temp["date_livraison"]) - ) - logger.info( - f" Date livraison: {facture_data_temp['date_livraison']}" - ) - champs_modifies.append("date_livraison") - - if modif_statut: - logger.info(" Modification statut...") - nouveau_statut = facture_data_temp["statut"] - doc.DO_Statut = nouveau_statut - champs_modifies.append("statut") - logger.info(f" Statut défini: {nouveau_statut}") - - if modif_ref: - logger.info(" Modification référence...") - try: - doc.DO_Ref = facture_data_temp["reference"] - champs_modifies.append("reference") - logger.info( - f" Référence définie: {facture_data_temp['reference']}" - ) - except Exception as e: - logger.warning(f" Référence non définie: {e}") - - logger.info(" Write() sans réassociation client...") - try: - doc.Write() - logger.info(" Write() réussi") - - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - if client_apres == client_code_initial: - logger.info(f" Client préservé: {client_apres}") - else: - logger.error( - f" Client perdu: {client_code_initial} → {client_apres}" - ) - - except Exception as e: - error_msg = str(e) - try: - sage_error = self.cial.CptaApplication.LastError - if sage_error: - error_msg = f"{sage_error.Description} (Code: {sage_error.Number})" - except Exception: - pass - - logger.error(f" Write() échoue: {error_msg}") - raise ValueError(f"Sage refuse: {error_msg}") - - elif modif_lignes: - logger.info(" REMPLACEMENT COMPLET DES LIGNES...") - - if modif_date: - doc.DO_Date = pywintypes.Time( - 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( - normaliser_date(facture_data_temp["date_livraison"]) - ) - logger.info(" Date livraison modifiée") - champs_modifies.append("date_livraison") - - nouvelles_lignes = facture_data["lignes"] - nb_nouvelles = len(nouvelles_lignes) - - logger.info( - f" {nb_lignes_initial} lignes existantes → {nb_nouvelles} nouvelles lignes" - ) - - try: - factory_lignes = doc.FactoryDocumentLigne - except Exception: - factory_lignes = doc.FactoryDocumentVenteLigne - - factory_article = self.cial.FactoryArticle - - if nb_lignes_initial > 0: - logger.info( - f" Suppression de {nb_lignes_initial} lignes existantes..." - ) - - for idx in range(nb_lignes_initial, 0, -1): - try: - ligne_p = factory_lignes.List(idx) - if ligne_p: - ligne = win32com.client.CastTo( - ligne_p, "IBODocumentLigne3" - ) - ligne.Read() - - ligne.Remove() - logger.debug(f" Ligne {idx} supprimée") - except Exception as e: - logger.warning( - f" Impossible de supprimer ligne {idx}: {e}" - ) - - logger.info(" Toutes les lignes existantes supprimées") - - logger.info(f" Ajout de {nb_nouvelles} nouvelles lignes...") - - for idx, ligne_data in enumerate(nouvelles_lignes, 1): - logger.info( - f" --- Ligne {idx}/{nb_nouvelles}: {ligne_data['article_code']} ---" - ) - - persist_article = factory_article.ReadReference( - ligne_data["article_code"] - ) - if not persist_article: - raise ValueError( - f"Article {ligne_data['article_code']} introuvable" - ) - - article_obj = win32com.client.CastTo( - persist_article, "IBOArticle3" - ) - article_obj.Read() - - ligne_persist = factory_lignes.Create() - - try: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentLigne3" - ) - except Exception: - ligne_obj = win32com.client.CastTo( - ligne_persist, "IBODocumentVenteLigne3" - ) - - quantite = float(ligne_data["quantite"]) - - try: - ligne_obj.SetDefaultArticleReference( - ligne_data["article_code"], quantite - ) - except Exception: - try: - ligne_obj.SetDefaultArticle(article_obj, quantite) - except Exception: - ligne_obj.DL_Design = ligne_data.get("designation", "") - ligne_obj.DL_Qte = quantite - - if ligne_data.get("prix_unitaire_ht"): - ligne_obj.DL_PrixUnitaire = float( - ligne_data["prix_unitaire_ht"] - ) - - if ligne_data.get("remise_pourcentage", 0) > 0: - try: - ligne_obj.DL_Remise01REM_Valeur = float( - ligne_data["remise_pourcentage"] - ) - # ligne_obj.DL_Remise01REM_Type = 0 - except Exception: - pass - - ligne_obj.Write() - logger.info(f" Ligne {idx} ajoutée") - - logger.info(f" {nb_nouvelles} nouvelles lignes ajoutées") - - logger.info(" Write() document après remplacement lignes...") - doc.Write() - logger.info(" Document écrit") - - import time - - time.sleep(0.5) - - doc.Read() - - client_obj = getattr(doc, "Client", None) - if client_obj: - client_obj.Read() - client_apres = getattr(client_obj, "CT_Num", "") - logger.info(f" Client après remplacement: {client_apres}") - else: - logger.error(" Client NULL après remplacement") - - champs_modifies.append("lignes") - - if reference_a_modifier is not None: - try: - ancienne_reference = getattr(doc, "DO_Ref", "") - nouvelle_reference = ( - str(reference_a_modifier) if reference_a_modifier else "" - ) - - logger.info( - f" Modification référence: '{ancienne_reference}' → '{nouvelle_reference}'" - ) - - doc.DO_Ref = nouvelle_reference - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("reference") - logger.info(" Référence modifiée avec succès") - except Exception as e: - logger.warning(f"Impossible de modifier la référence: {e}") - - if statut_a_modifier is not None: - try: - statut_actuel = getattr(doc, "DO_Statut", 0) - nouveau_statut = int(statut_a_modifier) - - if nouveau_statut != statut_actuel: - logger.info( - f" Modification statut: {statut_actuel} → {nouveau_statut}" - ) - - doc.DO_Statut = nouveau_statut - doc.Write() - - import time - - time.sleep(0.5) - - doc.Read() - - champs_modifies.append("statut") - logger.info(" Statut modifié avec succès") - except Exception as e: - logger.warning(f"Impossible de modifier le statut: {e}") - - logger.info(" Relecture finale...") - - import time - - time.sleep(0.5) - - doc.Read() - - client_obj_final = getattr(doc, "Client", None) - if client_obj_final: - client_obj_final.Read() - client_final = getattr(client_obj_final, "CT_Num", "") - else: - client_final = "" - - total_ht = float(getattr(doc, "DO_TotalHT", 0.0)) - total_ttc = float(getattr(doc, "DO_TotalTTC", 0.0)) - reference_finale = getattr(doc, "DO_Ref", "") - statut_final = getattr(doc, "DO_Statut", 0) - - date_livraison_final = None - - try: - date_livr = getattr(doc, "DO_DateLivr", None) - if date_livr: - date_livraison_final = date_livr.strftime("%Y-%m-%d") - except Exception: - pass - - logger.info(f" SUCCÈS: {numero} modifiée ") - logger.info(f" Totaux: {total_ht}€ HT / {total_ttc}€ TTC") - logger.info(f" Client final: {client_final}") - logger.info(f" Référence: {reference_finale}") - logger.info(f" Statut: {statut_final}") - - if date_livraison_final: - logger.info(f" Date livraison: {date_livraison_final}") - logger.info(f" Champs modifiés: {champs_modifies}") - - return { - "numero": numero, - "total_ht": total_ht, - "total_ttc": total_ttc, - "reference": reference_finale, - "date_livraison": date_livraison_final, - "champs_modifies": champs_modifies, - "statut": statut_final, - "client_code": client_final, - } - - except ValueError as e: - logger.error(f" ERREUR MÉTIER: {e}") - raise - - except Exception as e: - logger.error(f" ERREUR TECHNIQUE: {e}", exc_info=True) - - error_message = str(e) - if self.cial: - try: - err = self.cial.CptaApplication.LastError - if err: - error_message = ( - f"Erreur Sage: {err.Description} (Code: {err.Number})" - ) - except Exception: - pass - - raise RuntimeError(f"Erreur Sage: {error_message}") + def modifier_facture(self, numero: str, facture_data: dict) -> Dict: + """Modifie une facture""" + return modifier_document_vente( + self, numero, facture_data, TypeDocumentVente.FACTURE + ) def creer_article(self, article_data: dict) -> dict: """Crée un article dans Sage 100 - Version fusionnée complète""" @@ -9650,7 +7359,7 @@ class SageConnector: rows = cursor.fetchall() # ⚠️⚠️⚠️ VÉRIFIE CETTE LIGNE ⚠️⚠️⚠️ - collaborateurs = [self._row_to_collaborateur_dict(row) for row in rows] + collaborateurs = [row_to_collaborateur_dict(row) for row in rows] logger.info(f"✓ SQL: {len(collaborateurs)} collaborateurs") return collaborateurs @@ -9685,7 +7394,7 @@ class SageConnector: return None # ⚠️ UTILISER LA FONCTION DE CLASSE EXISTANTE - collaborateur = self._row_to_collaborateur_dict(row) + collaborateur = row_to_collaborateur_dict(row) logger.info( f"✓ SQL: Collaborateur {numero} avec {len(collaborateur)} champs" @@ -10004,33 +7713,3 @@ class SageConnector: f"❌ ERREUR MODIFICATION COLLABORATEUR {numero}: {e}", exc_info=True ) raise RuntimeError(f"Échec modification collaborateur: {str(e)}") - - def _row_to_collaborateur_dict(self, row): - """Convertit une ligne SQL en dictionnaire collaborateur""" - return { - "numero": row.CO_No, - "nom": _safe_strip(row.CO_Nom), # ⚠️ Utiliser _safe_strip - "prenom": _safe_strip(row.CO_Prenom), - "fonction": _safe_strip(row.CO_Fonction), - "adresse": _safe_strip(row.CO_Adresse), - "complement": _safe_strip(row.CO_Complement), - "code_postal": _safe_strip(row.CO_CodePostal), - "ville": _safe_strip(row.CO_Ville), - "code_region": _safe_strip(row.CO_CodeRegion), - "pays": _safe_strip(row.CO_Pays), - "service": _safe_strip(row.CO_Service), - "vendeur": bool(row.CO_Vendeur), - "caissier": bool(row.CO_Caissier), - "acheteur": bool(row.CO_Acheteur), - "telephone": _safe_strip(row.CO_Telephone), - "telecopie": _safe_strip(row.CO_Telecopie), - "email": _safe_strip(row.CO_EMail), - "tel_portable": _safe_strip(row.CO_TelPortable), - "matricule": _safe_strip(row.CO_Matricule), - "facebook": _safe_strip(row.CO_Facebook), - "linkedin": _safe_strip(row.CO_LinkedIn), - "skype": _safe_strip(row.CO_Skype), - "sommeil": bool(row.CO_Sommeil), - "chef_ventes": bool(row.CO_ChefVentes), - "numero_chef_ventes": row.CO_NoChefVentes if row.CO_NoChefVentes else None, - } diff --git a/utils/functions/data/create_doc.py b/utils/functions/data/create_doc.py index fd4afeb..c673f43 100644 --- a/utils/functions/data/create_doc.py +++ b/utils/functions/data/create_doc.py @@ -383,10 +383,302 @@ def _relire_document_final( return resultat +def modifier_document_vente( + self, numero: str, doc_data: dict, type_document: TypeDocumentVente +) -> Dict: + """ + Méthode unifiée de modification de documents de vente + RÉUTILISE les mêmes sous-méthodes que la création + """ + if not self.cial: + raise RuntimeError("Connexion Sage non établie") + + config = ConfigDocument(type_document) + logger.info(f"📝 === MODIFICATION {config.nom_document.upper()} {numero} ===") + + try: + with self._com_context(), self._lock_com: + # ========================================== + # 1. CHARGEMENT DOCUMENT + # ========================================== + logger.info("📂 Chargement document...") + factory = self.cial.FactoryDocumentVente + persist = None + + for type_test in [config.type_sage, 50]: + try: + persist_test = factory.ReadPiece(type_test, numero) + if persist_test: + persist = persist_test + logger.info(f" ✓ Document trouvé (type={type_test})") + break + except: + continue + + if not persist: + raise ValueError( + f"{config.nom_document.capitalize()} {numero} introuvable" + ) + + doc = win32com.client.CastTo(persist, "IBODocumentVente3") + doc.Read() + + # Vérifications + statut_actuel = getattr(doc, "DO_Statut", 0) + + if statut_actuel == 5: + raise ValueError(f"Le {config.nom_document} a déjà été transformé") + if statut_actuel == 6: + raise ValueError(f"Le {config.nom_document} est annulé") + + # ========================================== + # 2. SAUVEGARDE CLIENT INITIAL + # ========================================== + client_code_initial = "" + try: + client_obj = getattr(doc, "Client", None) + if client_obj: + client_obj.Read() + client_code_initial = getattr(client_obj, "CT_Num", "").strip() + logger.info(f" Client initial: {client_code_initial}") + except Exception as e: + logger.error(f" Erreur lecture client: {e}") + + if not client_code_initial: + raise ValueError("Client introuvable dans le document") + + # ========================================== + # 3. COMPTAGE LIGNES EXISTANTES + # ========================================== + nb_lignes_initial = 0 + try: + factory_lignes = getattr(doc, "FactoryDocumentLigne", None) or getattr( + doc, "FactoryDocumentVenteLigne", None + ) + index = 1 + while index <= 100: + try: + ligne_p = factory_lignes.List(index) + if ligne_p is None: + break + nb_lignes_initial += 1 + index += 1 + except: + break + logger.info(f" Lignes existantes: {nb_lignes_initial}") + except Exception as e: + logger.warning(f" Erreur comptage lignes: {e}") + + # ========================================== + # 4. ANALYSE MODIFICATIONS + # ========================================== + champs_modifies = [] + + modif_date = config.champ_date_principale in doc_data + modif_date_sec = ( + config.champ_date_secondaire + and config.champ_date_secondaire in doc_data + ) + modif_statut = "statut" in doc_data + modif_ref = "reference" in doc_data + modif_lignes = "lignes" in doc_data and doc_data["lignes"] is not None + + logger.info("📋 Modifications demandées:") + logger.info(f" {config.champ_date_principale}: {modif_date}") + if config.champ_date_secondaire: + logger.info(f" {config.champ_date_secondaire}: {modif_date_sec}") + logger.info(f" Statut: {modif_statut}") + logger.info(f" Référence: {modif_ref}") + logger.info(f" Lignes: {modif_lignes}") + + # Reporter référence et statut après les lignes + doc_data_temp = doc_data.copy() + reference_a_modifier = None + statut_a_modifier = None + + if modif_lignes: + if modif_ref: + reference_a_modifier = doc_data_temp.pop("reference") + modif_ref = False + if modif_statut: + statut_a_modifier = doc_data_temp.pop("statut") + modif_statut = False + + # ========================================== + # 5. TEST WRITE BASIQUE + # ========================================== + logger.info("🔍 Test Write() basique...") + try: + doc.Write() + doc.Read() + logger.info(" ✓ Write() basique OK") + except Exception as e: + logger.error(f" ❌ Document verrouillé: {e}") + raise ValueError(f"Document verrouillé: {e}") + + # ========================================== + # 6. MODIFICATIONS SIMPLES (sans lignes) + # ========================================== + if not modif_lignes and ( + modif_date or modif_date_sec or modif_statut or modif_ref + ): + logger.info("📝 Modifications simples...") + + if modif_date: + doc.DO_Date = pywintypes.Time( + normaliser_date(doc_data_temp.get(config.champ_date_principale)) + ) + champs_modifies.append(config.champ_date_principale) + + if modif_date_sec: + doc.DO_DateLivr = pywintypes.Time( + normaliser_date(doc_data_temp[config.champ_date_secondaire]) + ) + champs_modifies.append(config.champ_date_secondaire) + + if modif_statut: + doc.DO_Statut = doc_data_temp["statut"] + champs_modifies.append("statut") + + if modif_ref: + doc.DO_Ref = doc_data_temp["reference"] + champs_modifies.append("reference") + + # 🔥 CONFIGURATION SPÉCIFIQUE FACTURE + if type_document == TypeDocumentVente.FACTURE: + self._configurer_facture(doc) + + doc.Write() + logger.info(" ✓ Modifications appliquées") + + # ========================================== + # 7. REMPLACEMENT LIGNES + # ========================================== + elif modif_lignes: + logger.info("🔄 REMPLACEMENT COMPLET DES LIGNES...") + + # Dates + if modif_date: + doc.DO_Date = pywintypes.Time( + normaliser_date(doc_data_temp.get(config.champ_date_principale)) + ) + champs_modifies.append(config.champ_date_principale) + + if modif_date_sec: + doc.DO_DateLivr = pywintypes.Time( + normaliser_date(doc_data_temp[config.champ_date_secondaire]) + ) + champs_modifies.append(config.champ_date_secondaire) + + # 🔥 CONFIGURATION SPÉCIFIQUE FACTURE (avant lignes) + if type_document == TypeDocumentVente.FACTURE: + self._configurer_facture(doc) + + nouvelles_lignes = doc_data["lignes"] + nb_nouvelles = len(nouvelles_lignes) + + logger.info(f" {nb_lignes_initial} → {nb_nouvelles} lignes") + + try: + factory_lignes = doc.FactoryDocumentLigne + except: + factory_lignes = doc.FactoryDocumentVenteLigne + + factory_article = self.cial.FactoryArticle + + # Suppression lignes existantes + if nb_lignes_initial > 0: + logger.info(f" 🗑️ Suppression {nb_lignes_initial} lignes...") + for idx in range(nb_lignes_initial, 0, -1): + try: + ligne_p = factory_lignes.List(idx) + if ligne_p: + ligne = win32com.client.CastTo( + ligne_p, "IBODocumentLigne3" + ) + ligne.Read() + ligne.Remove() + except Exception as e: + logger.warning(f" Ligne {idx}: {e}") + logger.info(" ✓ Lignes supprimées") + + # Ajout nouvelles lignes avec REMISES + logger.info(f" ➕ Ajout {nb_nouvelles} lignes...") + for idx, ligne_data in enumerate(nouvelles_lignes, 1): + # 🔥 UTILISE _ajouter_ligne_document qui applique les remises + _ajouter_ligne_document( + cial=self.cial, + factory_lignes=factory_lignes, + factory_article=factory_article, + ligne_data=ligne_data, + idx=idx, + doc=doc, + ) + + logger.info(" ✓ Nouvelles lignes ajoutées avec remises") + + doc.Write() + time.sleep(0.5) + doc.Read() + + champs_modifies.append("lignes") + + # ========================================== + # 8. MODIFICATIONS REPORTÉES + # ========================================== + if reference_a_modifier is not None: + try: + logger.info( + f" 📝 Modification référence: '{reference_a_modifier}'" + ) + doc.DO_Ref = ( + str(reference_a_modifier) if reference_a_modifier else "" + ) + doc.Write() + time.sleep(0.5) + doc.Read() + champs_modifies.append("reference") + except Exception as e: + logger.warning(f" Référence: {e}") + + if statut_a_modifier is not None: + try: + logger.info(f" 📝 Modification statut: {statut_a_modifier}") + doc.DO_Statut = int(statut_a_modifier) + doc.Write() + time.sleep(0.5) + doc.Read() + champs_modifies.append("statut") + except Exception as e: + logger.warning(f" Statut: {e}") + + # ========================================== + # 9. RELECTURE FINALE + # ========================================== + resultat = self._relire_document_final(config, numero, doc_data) + resultat["champs_modifies"] = champs_modifies + + logger.info(f"✅ {config.nom_document.upper()} {numero} MODIFIÉ") + logger.info( + f" Totaux: {resultat['total_ht']}€ HT / {resultat['total_ttc']}€ TTC" + ) + logger.info(f" Champs modifiés: {champs_modifies}") + + return resultat + + except ValueError as e: + logger.error(f"❌ ERREUR MÉTIER: {e}") + raise + except Exception as e: + logger.error(f"❌ ERREUR TECHNIQUE: {e}", exc_info=True) + raise RuntimeError(f"Erreur Sage: {str(e)}") + + __all__ = [ "creer_document_vente", "_ajouter_ligne_document", "_configurer_facture", "_recuperer_numero_document", "_relire_document_final", + "modifier_document_vente", ] diff --git a/utils/functions/items_to_dict.py b/utils/functions/items_to_dict.py index 4fded2e..af14e56 100644 --- a/utils/functions/items_to_dict.py +++ b/utils/functions/items_to_dict.py @@ -123,6 +123,37 @@ def _row_to_collaborateur_dict(row) -> Optional[dict]: } +def row_to_collaborateur_dict(row): + """Convertit une ligne SQL en dictionnaire collaborateur""" + return { + "numero": row.CO_No, + "nom": _safe_strip(row.CO_Nom), # ⚠️ Utiliser _safe_strip + "prenom": _safe_strip(row.CO_Prenom), + "fonction": _safe_strip(row.CO_Fonction), + "adresse": _safe_strip(row.CO_Adresse), + "complement": _safe_strip(row.CO_Complement), + "code_postal": _safe_strip(row.CO_CodePostal), + "ville": _safe_strip(row.CO_Ville), + "code_region": _safe_strip(row.CO_CodeRegion), + "pays": _safe_strip(row.CO_Pays), + "service": _safe_strip(row.CO_Service), + "vendeur": bool(row.CO_Vendeur), + "caissier": bool(row.CO_Caissier), + "acheteur": bool(row.CO_Acheteur), + "telephone": _safe_strip(row.CO_Telephone), + "telecopie": _safe_strip(row.CO_Telecopie), + "email": _safe_strip(row.CO_EMail), + "tel_portable": _safe_strip(row.CO_TelPortable), + "matricule": _safe_strip(row.CO_Matricule), + "facebook": _safe_strip(row.CO_Facebook), + "linkedin": _safe_strip(row.CO_LinkedIn), + "skype": _safe_strip(row.CO_Skype), + "sommeil": bool(row.CO_Sommeil), + "chef_ventes": bool(row.CO_ChefVentes), + "numero_chef_ventes": row.CO_NoChefVentes if row.CO_NoChefVentes else None, + } + + def _row_to_tiers_dict(row) -> dict: """Convertit une ligne SQL en dictionnaire tiers""" tiers = { @@ -218,4 +249,5 @@ __all__ = [ "_contact_to_dict", "_row_to_contact_dict", "_row_to_tiers_dict", + "row_to_collaborateur_dict" ]