From 557f43bd18ecf7c0cdc0371ce66407a2409844c5 Mon Sep 17 00:00:00 2001 From: mickael Date: Fri, 26 Dec 2025 09:35:25 +0100 Subject: [PATCH] =?UTF-8?q?creer=5Fclient=20op=C3=A9rationelle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diagnostic_crystal.txt | 12 + sage_connector.py | 927 ++++++++++++++++++----------------------- test.py | 597 +++++++++++++------------- 3 files changed, 717 insertions(+), 819 deletions(-) create mode 100644 diagnostic_crystal.txt diff --git a/diagnostic_crystal.txt b/diagnostic_crystal.txt new file mode 100644 index 0000000..77bacdb --- /dev/null +++ b/diagnostic_crystal.txt @@ -0,0 +1,12 @@ +====================================================================== +DIAGNOSTIC CRYSTAL REPORTS +====================================================================== + +Installation détectée: True +DLL trouvées: +ProgID valides: +Architecture OK: False + +PROBLÈMES: + +SOLUTIONS: diff --git a/sage_connector.py b/sage_connector.py index f00e3d5..5470965 100644 --- a/sage_connector.py +++ b/sage_connector.py @@ -5149,39 +5149,19 @@ class SageConnector: def creer_client(self, client_data: Dict) -> Dict: """ - Creation d'un tiers Sage 100c (Client/Fournisseur/Salarie/Autre) - Version sans catégories pour test initial + Creation client Sage - Version corrigée pour erreur cohérence """ if not self.cial: raise RuntimeError("Connexion Sage non etablie") try: with self._com_context(), self._lock_com: - # ============================================================ - # CONSTANTES - # ============================================================ - LENGTHS = { - "CT_Num": 17, "CT_Intitule": 69, "CT_Qualite": 17, - "CT_Classement": 17, "CT_Raccourci": 7, "CT_Contact": 35, - "CT_Adresse": 35, "CT_Complement": 35, "CT_CodePostal": 9, - "CT_Ville": 35, "CT_CodeRegion": 25, "CT_Pays": 35, - "CT_Telephone": 21, "CT_Telecopie": 21, "CT_EMail": 69, - "CT_Site": 69, "CT_Facebook": 35, "CT_LinkedIn": 35, - "CT_Siret": 15, "CT_Identifiant": 25, "CT_Ape": 7, - "CT_NumPayeur": 17, "CT_NumCentrale": 17, "CT_Commentaire": 35, - "CT_Statistique": 21, "CT_Coface": 25, "CT_SvFormeJuri": 33, - "CT_SvEffectif": 11, "CT_SvRegul": 3, "CT_SvCotation": 5, - "CT_SvObjetMaj": 61, "CT_EdiCode": 23, "CT_EdiCodeSage": 9, - "CT_RepresentInt": 35, "CT_RepresentNIF": 25, "CT_LangueISO2": 3, - "CT_FEAutreIdentifVal": 81, "CG_NumPrinc": 13, "CA_Num": 13, "DN_Id": 37, - } - - logger.info("=" * 60) - logger.info("[+] CREATION TIERS SAGE - DEBUT") - logger.info("=" * 60) + logger.info("=" * 80) + logger.info("[CREATION CLIENT SAGE - DIAGNOSTIC COMPLET]") + logger.info("=" * 80) # ============================================================ - # FONCTIONS UTILITAIRES + # UTILITAIRES # ============================================================ def clean_str(value, max_len: int) -> str: if value is None or str(value).lower() in ('none', 'null', ''): @@ -5204,356 +5184,366 @@ class SageConnector: except (ValueError, TypeError): return default - def safe_bool_to_int(value, default=0): - if value is None: - return default - return 1 if value else 0 + def try_set_attribute(obj, attr_name, value, variants=None): + """Essaie de definir un attribut avec plusieurs variantes de noms""" + if variants is None: + variants = [attr_name] + else: + variants = [attr_name] + variants + + for variant in variants: + try: + if hasattr(obj, variant): + setattr(obj, variant, value) + logger.debug(f" {variant} = {value} [OK]") + return True + except Exception as e: + logger.debug(f" {variant} echec: {str(e)[:50]}") + + return False # ============================================================ # VALIDATION # ============================================================ if not client_data.get("intitule"): - raise ValueError("Le champ 'intitule' est obligatoire") + raise ValueError("intitule obligatoire") if not client_data.get("numero"): - raise ValueError("Le champ 'numero' est obligatoire") + raise ValueError("numero obligatoire") - intitule = clean_str(client_data["intitule"], LENGTHS["CT_Intitule"]) - numero = clean_str(client_data["numero"], LENGTHS["CT_Num"]).upper() - qualite = clean_str(client_data.get("qualite", "CLI"), LENGTHS["CT_Qualite"]) + intitule = clean_str(client_data["intitule"], 69) + numero = clean_str(client_data["numero"], 17).upper() type_tiers = safe_int(client_data.get("type_tiers"), 0) + + # ============================================================ + # ETAPE 1 : CREATION OBJET + # ============================================================ + logger.info("[ETAPE 1] CREATION OBJET") - logger.info(f" Numero: {numero}") - logger.info(f" Intitule: {intitule}") - logger.info(f" Type tiers: {type_tiers}") - logger.info(f" Qualite: {qualite}") - - # ============================================================ - # COMPTE GENERAL SELON TYPE - # ============================================================ - COMPTES_DEFAUT_TYPE = { - 0: "4110000", # Client - 1: "4010000", # Fournisseur - 2: "421", # Salarie - 3: "471", # Autre - } - compte_demande = client_data.get("compte_general") or client_data.get("compte_collectif") - compte = clean_str( - compte_demande or COMPTES_DEFAUT_TYPE.get(type_tiers, "4110000"), - LENGTHS["CG_NumPrinc"] - ) - - # ============================================================ - # SELECTION DE LA FACTORY SELON TYPE_TIERS - # ============================================================ factory_map = { - 0: ("FactoryClient", "IBOClient3", "CLIENT"), - 1: ("FactoryFourniss", "IBOFournisseur3", "FOURNISSEUR"), - 2: ("FactorySalarie", "IBOSalarie3", "SALARIE"), - 3: ("FactoryAutre", "IBOAutre3", "AUTRE"), + 0: ("FactoryClient", "IBOClient3"), + 1: ("FactoryFourniss", "IBOFournisseur3"), + 2: ("FactorySalarie", "IBOSalarie3"), + 3: ("FactoryAutre", "IBOAutre3"), } - if type_tiers not in factory_map: - raise ValueError(f"Type de tiers invalide: {type_tiers}") - - factory_name, interface_name, type_label = factory_map[type_tiers] - logger.info(f"[*] Utilisation de {factory_name} pour type {type_label}") - - try: - factory = getattr(self.cial.CptaApplication, factory_name) - except AttributeError: - raise RuntimeError(f"Factory {factory_name} non disponible") - + factory_name, interface_name = factory_map[type_tiers] + factory = getattr(self.cial.CptaApplication, factory_name) persist = factory.Create() client = win32com.client.CastTo(persist, interface_name) - client.SetDefault() - logger.info(f"[OK] Objet {type_label} cree") - - # ============================================================ - # DEBUG : LISTER LES PROPRIETES DISPONIBLES - # ============================================================ - logger.info("[?] Propriétés COM disponibles:") - try: - # Lister toutes les propriétés de l'objet COM - if hasattr(client, '_prop_map_get_'): - props = list(client._prop_map_get_.keys()) - logger.info(f" Propriétés GET: {', '.join(sorted(props)[:20])}...") - if hasattr(client, '_prop_map_put_'): - props = list(client._prop_map_put_.keys()) - logger.info(f" Propriétés PUT: {', '.join(sorted(props)[:20])}...") - except Exception as e: - logger.warning(f" Impossible de lister: {e}") - - # ============================================================ - # CHAMPS OBLIGATOIRES - # ============================================================ - logger.info("[*] Configuration champs obligatoires...") + logger.info(f" Objet cree: {interface_name}") + + # ============================================================ + # ETAPE 2 : CONFIGURATION OBLIGATOIRE MINIMALE + # ============================================================ + logger.info("[ETAPE 2] CONFIGURATION OBLIGATOIRE") + + # 1. DEFINIR LES CHAMPS MINIMUMS D'ABORD client.CT_Intitule = intitule - logger.info(f" CT_Intitule = {intitule}") - client.CT_Num = numero logger.info(f" CT_Num = {numero}") + logger.info(f" CT_Intitule = {intitule}") + qualite = clean_str(client_data.get("qualite", "CLI"), 17) if qualite: client.CT_Qualite = qualite logger.info(f" CT_Qualite = {qualite}") - # ============================================================ - # COMPTE GENERAL - # ============================================================ - logger.info("[*] Configuration compte general...") - - compte_trouve = False - factory_compte = self.cial.CptaApplication.FactoryCompteG - - comptes_alternatifs = { - 0: ["4110000", "411000", "41100000", "411"], - 1: ["4010000", "401000", "40100000", "401"], - 2: ["421", "4210000", "421000"], - 3: ["471", "4710000", "471000"], - } - - if compte: + # 2. APPLIQUER SETDEFAULT + client.SetDefault() + logger.info(" SetDefault() applique") + + if client_data.get("raccourci"): + raccourci = clean_str(client_data["raccourci"], 7).upper().strip() + + # Vérifier unicité dans Sage try: - persist_compte = factory_compte.ReadNumero(compte) + factory_client = self.cial.CptaApplication.FactoryClient + exist_client = factory_client.ReadRaccourci(raccourci) + + if exist_client: + logger.warning(f" CT_Raccourci = {raccourci} [EXISTE DEJA - ignoré]") + else: + client.CT_Raccourci = raccourci + logger.info(f" CT_Raccourci = {raccourci} [OK]") + except Exception as e: + # Si ReadRaccourci n'existe pas, essayer direct + try: + client.CT_Raccourci = raccourci + logger.info(f" CT_Raccourci = {raccourci} [OK]") + except Exception as e2: + logger.warning(f" CT_Raccourci = {raccourci} [ECHEC: {e2}]") + + # 3. FORCER CT_Type si nécessaire + try: + if not hasattr(client, 'CT_Type') or client.CT_Type is None: + client.CT_Type = type_tiers + logger.info(f" CT_Type force a {type_tiers}") + except: + pass + + # 4. COMPTE GENERAL (OBLIGATOIRE) + COMPTES_DEFAUT = {0: "4110000", 1: "4010000", 2: "421", 3: "471"} + compte = clean_str( + client_data.get("compte_general") or COMPTES_DEFAUT.get(type_tiers, "4110000"), + 13 + ) + + factory_compte = self.cial.CptaApplication.FactoryCompteG + compte_trouve = False + + comptes_a_tester = [ + compte, + COMPTES_DEFAUT.get(type_tiers, "4110000"), + "4110000", "411000", "411", # Clients + "4010000", "401000", "401", # Fournisseurs + ] + + for test_compte in comptes_a_tester: + try: + persist_compte = factory_compte.ReadNumero(test_compte) if persist_compte: compte_obj = win32com.client.CastTo(persist_compte, "IBOCompteG3") compte_obj.Read() - client.CompteGPrinc = compte_obj - compte_trouve = True - logger.info(f" CompteGPrinc: '{compte}' [OK]") - except Exception as e: - logger.warning(f" CompteGPrinc '{compte}' non trouve: {e}") - - if not compte_trouve: - comptes_a_essayer = comptes_alternatifs.get(type_tiers, comptes_alternatifs[0]) - for alt in comptes_a_essayer: - try: - persist_compte = factory_compte.ReadNumero(alt) - if persist_compte: - compte_obj = win32com.client.CastTo(persist_compte, "IBOCompteG3") - compte_obj.Read() + + # Vérifier que c'est un compte de détail (Type 0) + type_compte = getattr(compte_obj, 'CG_Type', None) + + if type_compte == 0: client.CompteGPrinc = compte_obj + compte = test_compte compte_trouve = True - compte = alt - logger.info(f" CompteGPrinc: '{alt}' [OK]") + logger.info(f" CompteGPrinc = {test_compte} (Type: {type_compte}) [OK]") + break + else: + logger.debug(f" Compte {test_compte} - Type {type_compte} incompatible") + except Exception as e: + logger.debug(f" Compte {test_compte} - erreur: {e}") + + if not compte_trouve: + raise RuntimeError("Aucun compte general valide trouve") + + # 5. CATEGORIES VIA OBJETS (si nécessaire) + logger.info(" Configuration categories:") + + # Catégorie Tarifaire + try: + factory_cat_tarif = self.cial.CptaApplication.FactoryCategorieTarif + # Essayer d'abord l'ID 0, sinon 1 + for cat_id in ["0", "1"]: + try: + persist_cat = factory_cat_tarif.ReadIntitule(cat_id) + if persist_cat: + cat_tarif_obj = win32com.client.CastTo(persist_cat, "IBOCategorieTarif3") + cat_tarif_obj.Read() + client.CatTarif = cat_tarif_obj + logger.info(f" CatTarif = {cat_id} [OK]") break except: continue - - if not compte_trouve: - raise ValueError(f"Aucun compte general trouve pour type {type_label}") - - # ============================================================ - # CATEGORIES - OPTIONNELLES POUR LE MOMENT - # ============================================================ - logger.info("[*] Configuration categories (optionnel)...") - - cat_tarif = safe_int(client_data.get("categorie_tarifaire") or client_data.get("cat_tarif"), 1) - cat_compta = safe_int(client_data.get("categorie_comptable") or client_data.get("cat_compta"), 1) - - # Tenter via FactoryCatTarif - logger.info(f" Tentative catégorie tarifaire: {cat_tarif}") - try: - factory_cat_tarif = self.cial.CptaApplication.FactoryCatTarif - persist_cat = factory_cat_tarif.ReadIntitule(str(cat_tarif)) - if persist_cat: - cat_obj = win32com.client.CastTo(persist_cat, "IBOCatTarif3") - cat_obj.Read() - client.CatTarif = cat_obj - logger.info(f" CatTarif = {cat_tarif} [OK via Factory]") except Exception as e: - logger.warning(f" CatTarif non définie: {e}") - - # Tenter via FactoryCatCompta - logger.info(f" Tentative catégorie comptable: {cat_compta}") + logger.warning(f" CatTarif erreur: {e}") + + # Catégorie Comptable try: - factory_cat_compta = self.cial.CptaApplication.FactoryCatCompta - persist_cat = factory_cat_compta.ReadIntitule(str(cat_compta)) - if persist_cat: - cat_obj = win32com.client.CastTo(persist_cat, "IBOCatCompta3") - cat_obj.Read() - client.CatCompta = cat_obj - logger.info(f" CatCompta = {cat_compta} [OK via Factory]") + factory_cat_compta = self.cial.CptaApplication.FactoryCategorieCompta + for cat_id in ["0", "1"]: + try: + persist_cat = factory_cat_compta.ReadIntitule(cat_id) + if persist_cat: + cat_compta_obj = win32com.client.CastTo(persist_cat, "IBOCategorieCompta3") + cat_compta_obj.Read() + client.CatCompta = cat_compta_obj + logger.info(f" CatCompta = {cat_id} [OK]") + break + except: + continue except Exception as e: - logger.warning(f" CatCompta non définie: {e}") - - # Autres catégories optionnelles - categories_opt = { - "Period": safe_int(client_data.get("periode_reglement") or client_data.get("period"), 1), - "Expedition": safe_int(client_data.get("mode_expedition") or client_data.get("expedition"), 1), - "Condition": safe_int(client_data.get("condition_livraison") or client_data.get("condition"), 1), - "Risque": safe_int(client_data.get("niveau_risque") or client_data.get("risque"), 1), - } - for attr, val in categories_opt.items(): - if val is not None: - try: - setattr(client, attr, val) - logger.debug(f" {attr} = {val}") - except Exception as e: - logger.debug(f" {attr} non défini: {e}") + logger.warning(f" CatCompta erreur: {e}") # ============================================================ - # IDENTIFICATION + # ETAPE 3 : IDENTIFICATION # ============================================================ - logger.info("[*] Identification...") + logger.info("[ETAPE 3] IDENTIFICATION") - id_fields = { - "CT_Classement": ("classement", LENGTHS["CT_Classement"]), - "CT_Raccourci": ("raccourci", LENGTHS["CT_Raccourci"]), - "CT_Siret": ("siret", LENGTHS["CT_Siret"]), - "CT_Identifiant": ("tva_intra", LENGTHS["CT_Identifiant"]), - "CT_Ape": ("code_naf", LENGTHS["CT_Ape"]), - } - for attr_sage, (key_data, max_len) in id_fields.items(): - val = client_data.get(key_data) - if val: - try: - setattr(client, attr_sage, clean_str(val, max_len)) - except: - pass + if client_data.get("classement"): + try_set_attribute(client, "CT_Classement", clean_str(client_data["classement"], 17)) - if client_data.get("banque_num") is not None: - try: - client.BT_Num = safe_int(client_data["banque_num"]) - except: - pass + if client_data.get("raccourci"): + raccourci = clean_str(client_data["raccourci"], 7).upper() + try_set_attribute(client, "CT_Raccourci", raccourci) - if client_data.get("devise") is not None: - try: - client.N_Devise = safe_int(client_data["devise"], 0) - except: - pass - - if client_data.get("type_nif") is not None: - try: - client.CT_TypeNIF = safe_int(client_data["type_nif"]) - except: - pass - - # ============================================================ - # ADRESSE - # ============================================================ - logger.info("[*] Adresse...") + if client_data.get("siret"): + try_set_attribute(client, "CT_Siret", clean_str(client_data["siret"], 15)) - adresse_fields = { - "CT_Contact": ("contact", LENGTHS["CT_Contact"]), - "CT_Adresse": ("adresse", LENGTHS["CT_Adresse"]), - "CT_Complement": ("complement", LENGTHS["CT_Complement"]), - "CT_CodePostal": ("code_postal", LENGTHS["CT_CodePostal"]), - "CT_Ville": ("ville", LENGTHS["CT_Ville"]), - "CT_CodeRegion": ("region", LENGTHS["CT_CodeRegion"]), - "CT_Pays": ("pays", LENGTHS["CT_Pays"]), - } - for attr_sage, (key_data, max_len) in adresse_fields.items(): - val = client_data.get(key_data) - if val and str(val).lower() not in ('none', 'null'): - try: - setattr(client, attr_sage, clean_str(val, max_len)) - except: - pass - - # ============================================================ - # COMMUNICATION - # ============================================================ - logger.info("[*] Communication...") + if client_data.get("tva_intra"): + try_set_attribute(client, "CT_Identifiant", clean_str(client_data["tva_intra"], 25)) - telecom_fields = { - "CT_Telephone": ("telephone", LENGTHS["CT_Telephone"]), - "CT_Telecopie": ("telecopie", LENGTHS["CT_Telecopie"]), - "CT_EMail": ("email", LENGTHS["CT_EMail"]), - "CT_Site": ("site_web", LENGTHS["CT_Site"]), - "CT_Facebook": ("facebook", LENGTHS["CT_Facebook"]), - "CT_LinkedIn": ("linkedin", LENGTHS["CT_LinkedIn"]), - } - for attr_sage, (key_data, max_len) in telecom_fields.items(): - val = client_data.get(key_data) - if val and str(val).lower() not in ('none', 'null'): - try: - setattr(client, attr_sage, clean_str(val, max_len)) - except: - pass + if client_data.get("code_naf"): + try_set_attribute(client, "CT_Ape", clean_str(client_data["code_naf"], 7)) # ============================================================ - # TAUX + # ETAPE 4 : ADRESSE # ============================================================ - logger.info("[*] Taux...") + logger.info("[ETAPE 4] ADRESSE") + + # CT_Contact - DOUBLE AFFECTATION (client ET adresse) + if client_data.get("contact"): + contact_nom = clean_str(client_data["contact"], 35) + + # Variante 1 : Sur l'objet client directement + try: + client.CT_Contact = contact_nom + logger.info(f" CT_Contact (client) = {contact_nom} [OK]") + except Exception as e: + logger.warning(f" CT_Contact (client) [ECHEC: {e}]") + + # Variante 2 : Via l'objet Adresse + try: + adresse_obj = client.Adresse + adresse_obj.Contact = contact_nom + logger.info(f" Contact (adresse) = {contact_nom} [OK]") + except Exception as e: + logger.warning(f" Contact (adresse) [ECHEC: {e}]") + + # Puis le reste de l'adresse + try: + adresse_obj = client.Adresse + logger.info(" Objet Adresse OK") + + if client_data.get("adresse"): + adresse_obj.Adresse = clean_str(client_data["adresse"], 35) + + if client_data.get("complement"): + adresse_obj.Complement = clean_str(client_data["complement"], 35) + + if client_data.get("code_postal"): + adresse_obj.CodePostal = clean_str(client_data["code_postal"], 9) + + if client_data.get("ville"): + adresse_obj.Ville = clean_str(client_data["ville"], 35) + + if client_data.get("region"): + adresse_obj.CodeRegion = clean_str(client_data["region"], 25) + + if client_data.get("pays"): + adresse_obj.Pays = clean_str(client_data["pays"], 35) + + except Exception as e: + logger.error(f" Adresse erreur: {e}") + + # ============================================================ + # ETAPE 5 : TELECOM + # ============================================================ + logger.info("[ETAPE 5] TELECOM") + + try: + telecom_obj = client.Telecom + logger.info(" Objet Telecom OK") + + if client_data.get("telephone"): + telecom_obj.Telephone = clean_str(client_data["telephone"], 21) + + if client_data.get("telecopie"): + telecom_obj.Telecopie = clean_str(client_data["telecopie"], 21) + + if client_data.get("email"): + telecom_obj.EMail = clean_str(client_data["email"], 69) + + if client_data.get("site_web"): + telecom_obj.Site = clean_str(client_data["site_web"], 69) + + if client_data.get("portable"): + portable = clean_str(client_data["portable"], 21) + try_set_attribute(telecom_obj, "Portable", portable) + logger.info(f" Portable = {portable}") + + # CT_Facebook + if client_data.get("facebook"): + facebook = clean_str(client_data["facebook"], 69) # URL ou @username + # Essayer plusieurs variantes + if not try_set_attribute(telecom_obj, "Facebook", facebook): + try_set_attribute(client, "CT_Facebook", facebook) + logger.info(f" Facebook = {facebook}") + + # CT_LinkedIn + if client_data.get("linkedin"): + linkedin = clean_str(client_data["linkedin"], 69) # URL ou profil + # Essayer plusieurs variantes + if not try_set_attribute(telecom_obj, "LinkedIn", linkedin): + try_set_attribute(client, "CT_LinkedIn", linkedin) + logger.info(f" LinkedIn = {linkedin}") + + except Exception as e: + logger.error(f" Telecom erreur: {e}") + + # ============================================================ + # ETAPE 6 : TAUX + # ============================================================ + logger.info("[ETAPE 6] TAUX") + for i in range(1, 5): val = client_data.get(f"taux{i:02d}") if val is not None: - try: - setattr(client, f"CT_Taux{i:02d}", safe_float(val)) - except: - pass + try_set_attribute(client, f"CT_Taux{i:02d}", safe_float(val)) # ============================================================ - # COMMERCIAL + # ETAPE 7 : STATISTIQUES # ============================================================ - logger.info("[*] Commercial...") + logger.info("[ETAPE 7] STATISTIQUES") - if client_data.get("encours_autorise") is not None: - try: - client.CT_Encours = safe_float(client_data["encours_autorise"]) - except: - pass + stat01 = client_data.get("statistique01") or client_data.get("secteur") + if stat01: + try_set_attribute(client, "CT_Statistique01", clean_str(stat01, 21)) - if client_data.get("assurance_credit") is not None: - try: - client.CT_Assurance = safe_float(client_data["assurance_credit"]) - except: - pass + for i in range(2, 11): + val = client_data.get(f"statistique{i:02d}") + if val: + try_set_attribute(client, f"CT_Statistique{i:02d}", clean_str(val, 21)) + + # ============================================================ + # ETAPE 8 : COMMERCIAL + # ============================================================ + logger.info("[ETAPE 8] COMMERCIAL") - if client_data.get("num_payeur"): - try: - client.CT_NumPayeur = clean_str(client_data["num_payeur"], LENGTHS["CT_NumPayeur"]) - except: - pass + if client_data.get("encours_autorise"): + try_set_attribute(client, "CT_Encours", safe_float(client_data["encours_autorise"])) + + if client_data.get("assurance_credit"): + try_set_attribute(client, "CT_Assurance", safe_float(client_data["assurance_credit"])) if client_data.get("langue") is not None: - try: - client.CT_Langue = safe_int(client_data["langue"]) - except: - pass + try_set_attribute(client, "CT_Langue", safe_int(client_data["langue"])) - if client_data.get("langue_iso2"): - try: - client.CT_LangueISO2 = clean_str(client_data["langue_iso2"], LENGTHS["CT_LangueISO2"]) - except: - pass - - commercial = client_data.get("commercial_code") or client_data.get("collaborateur") - if commercial is not None: - try: - client.CO_No = safe_int(commercial) - except: - pass + # Collaborateur + if client_data.get("commercial_code") is not None: + co_no = safe_int(client_data["commercial_code"]) + if not try_set_attribute(client, "CO_No", co_no): + try: + factory_collab = self.cial.CptaApplication.FactoryCollaborateur + persist_collab = factory_collab.ReadIntitule(str(co_no)) + if persist_collab: + collab_obj = win32com.client.CastTo(persist_collab, "IBOCollaborateur3") + collab_obj.Read() + client.Collaborateur = collab_obj + logger.debug(f" Collaborateur (objet) = {co_no} [OK]") + except Exception as e: + logger.debug(f" Collaborateur echec: {e}") # ============================================================ - # FACTURATION + # ETAPE 9 : FACTURATION # ============================================================ - logger.info("[*] Facturation...") + logger.info("[ETAPE 9] FACTURATION") - bool_fields = { - "CT_Lettrage": "lettrage_auto", - "CT_Sommeil": "est_en_sommeil", - "CT_Prospect": "est_prospect", - } - for attr, key in bool_fields.items(): - val = client_data.get(key) - if val is not None: - try: - setattr(client, attr, safe_bool_to_int(val)) - except: - pass - elif key == "est_en_sommeil" and "est_actif" in client_data: - try: - setattr(client, attr, safe_bool_to_int(not client_data["est_actif"])) - except: - pass + try_set_attribute(client, "CT_Lettrage", 1 if client_data.get("lettrage_auto", True) else 0) + try_set_attribute(client, "CT_Sommeil", 0 if client_data.get("est_actif", True) else 1) + try_set_attribute(client, "CT_Facture", safe_int(client_data.get("type_facture", 1))) - int_fields = { - "CT_Facture": "type_facture", + if client_data.get("est_prospect") is not None: + try_set_attribute(client, "CT_Prospect", 1 if client_data["est_prospect"] else 0) + + factu_map = { "CT_BLFact": "bl_en_facture", "CT_Saut": "saut_page", "CT_ValidEch": "validation_echeance", @@ -5562,233 +5552,139 @@ class SageConnector: "CT_NotPenal": "exclure_penalites", "CT_BonAPayer": "bon_a_payer", } - for attr, key in int_fields.items(): - val = client_data.get(key) - if val is not None: - try: - setattr(client, attr, safe_int(val)) - except: - pass + for attr, key in factu_map.items(): + if client_data.get(key) is not None: + try_set_attribute(client, attr, safe_int(client_data[key])) # ============================================================ - # LOGISTIQUE (je garde compact pour la longueur) + # ETAPE 10 : LOGISTIQUE # ============================================================ - logger.info("[*] Logistique...") + logger.info("[ETAPE 10] LOGISTIQUE") - logistique = { + logistique_map = { "CT_PrioriteLivr": "priorite_livraison", "CT_LivrPartielle": "livraison_partielle", "CT_DelaiTransport": "delai_transport", "CT_DelaiAppro": "delai_appro", } - for attr, key in logistique.items(): - val = client_data.get(key) - if val is not None: - try: - setattr(client, attr, safe_int(val)) - except: - pass - - # Jours commande/livraison - if client_data.get("jours_commande"): - jours_map = [ - ("CT_OrderDay01", "lundi"), ("CT_OrderDay02", "mardi"), - ("CT_OrderDay03", "mercredi"), ("CT_OrderDay04", "jeudi"), - ("CT_OrderDay05", "vendredi"), ("CT_OrderDay06", "samedi"), - ("CT_OrderDay07", "dimanche"), - ] - for attr_sage, jour_key in jours_map: - val = client_data["jours_commande"].get(jour_key) - if val is not None: - try: - setattr(client, attr_sage, safe_int(val)) - except: - pass - - if client_data.get("jours_livraison"): - jours_map = [ - ("CT_DeliveryDay01", "lundi"), ("CT_DeliveryDay02", "mardi"), - ("CT_DeliveryDay03", "mercredi"), ("CT_DeliveryDay04", "jeudi"), - ("CT_DeliveryDay05", "vendredi"), ("CT_DeliveryDay06", "samedi"), - ("CT_DeliveryDay07", "dimanche"), - ] - for attr_sage, jour_key in jours_map: - val = client_data["jours_livraison"].get(jour_key) - if val is not None: - try: - setattr(client, attr_sage, safe_int(val)) - except: - pass - - for date_attr, date_key in [ - ("CT_DateFermeDebut", "date_fermeture_debut"), - ("CT_DateFermeFin", "date_fermeture_fin") - ]: - if client_data.get(date_key): - try: - setattr(client, date_attr, client_data[date_key]) - except: - pass + for attr, key in logistique_map.items(): + if client_data.get(key) is not None: + try_set_attribute(client, attr, safe_int(client_data[key])) # ============================================================ - # STATISTIQUES - # ============================================================ - logger.info("[*] Statistiques...") - - stat01 = client_data.get("statistique01") or client_data.get("secteur") - if stat01: - try: - client.CT_Statistique01 = clean_str(stat01, LENGTHS["CT_Statistique"]) - except: - pass - - for i in range(2, 11): - val = client_data.get(f"statistique{i:02d}") - if val: - try: - setattr(client, f"CT_Statistique{i:02d}", clean_str(val, LENGTHS["CT_Statistique"])) - except: - pass - - # ============================================================ - # COMMENTAIRE + # ETAPE 11 : COMMENTAIRE # ============================================================ if client_data.get("commentaire"): - try: - client.CT_Commentaire = clean_str(client_data["commentaire"], LENGTHS["CT_Commentaire"]) - except: - pass + try_set_attribute(client, "CT_Commentaire", clean_str(client_data["commentaire"], 35)) # ============================================================ - # ANALYTIQUE + # ETAPE 12 : ANALYTIQUE # ============================================================ - logger.info("[*] Analytique...") + logger.info("[ETAPE 12] ANALYTIQUE") if client_data.get("section_analytique"): - try: - client.CA_Num = clean_str(client_data["section_analytique"], LENGTHS["CA_Num"]) - except: - pass - - if client_data.get("section_analytique_ifrs"): - try: - client.CA_NumIFRS = clean_str(client_data["section_analytique_ifrs"], LENGTHS["CA_Num"]) - except: - pass - - if client_data.get("plan_analytique") is not None: - try: - client.N_Analytique = safe_int(client_data["plan_analytique"]) - except: - pass - - if client_data.get("plan_analytique_ifrs") is not None: - try: - client.N_AnalytiqueIFRS = safe_int(client_data["plan_analytique_ifrs"]) - except: - pass + try_set_attribute(client, "CA_Num", clean_str(client_data["section_analytique"], 13)) # ============================================================ - # ORGANISATION + # ETAPE 13 : ORGANISATION # ============================================================ - logger.info("[*] Organisation...") + logger.info("[ETAPE 13] ORGANISATION") - org_fields = { - "DE_No": "depot_code", - "EB_No": "etablissement_code", - "MR_No": "mode_reglement_code", - "CAL_No": "calendrier_code", - } - for attr_sage, key_data in org_fields.items(): - val = client_data.get(key_data) - if val is not None: + # Mode reglement + if client_data.get("mode_reglement_code") is not None: + mr_no = safe_int(client_data["mode_reglement_code"]) + if not try_set_attribute(client, "MR_No", mr_no): try: - setattr(client, attr_sage, safe_int(val)) - except: - pass - - if client_data.get("num_centrale"): - try: - client.CT_NumCentrale = clean_str(client_data["num_centrale"], LENGTHS["CT_NumCentrale"]) - except: - pass + factory_mr = self.cial.CptaApplication.FactoryModeRegl + persist_mr = factory_mr.ReadIntitule(str(mr_no)) + if persist_mr: + mr_obj = win32com.client.CastTo(persist_mr, "IBOModeRegl3") + mr_obj.Read() + client.ModeRegl = mr_obj + logger.debug(f" ModeRegl (objet) = {mr_no} [OK]") + except Exception as e: + logger.debug(f" ModeRegl echec: {e}") - # ============================================================ - # SURVEILLANCE (version compacte) - # ============================================================ - logger.info("[*] Surveillance...") - - if client_data.get("coface"): - try: - client.CT_Coface = clean_str(client_data["coface"], LENGTHS["CT_Coface"]) - except: - pass - + # CT_Surveillance - DOIT être défini en PREMIER (0 ou 1) if client_data.get("surveillance_active") is not None: + surveillance = 1 if client_data["surveillance_active"] else 0 try: - client.CT_Surveillance = safe_int(client_data["surveillance_active"]) - except: - pass - - forme_juri = client_data.get("forme_juridique") or client_data.get("sv_forme_juri") - if forme_juri: - try: - client.CT_SvFormeJuri = clean_str(forme_juri, LENGTHS["CT_SvFormeJuri"]) - except: - pass - - effectif = client_data.get("effectif") or client_data.get("sv_effectif") - if effectif: - try: - client.CT_SvEffectif = clean_str(effectif, LENGTHS["CT_SvEffectif"]) - except: - pass - - ca = client_data.get("sv_chiffre_affaires") or client_data.get("ca_annuel") - if ca is not None: - try: - client.CT_SvCA = safe_float(ca) - except: - pass + client.CT_Surveillance = surveillance + logger.info(f" CT_Surveillance = {surveillance} [OK]") + except Exception as e: + logger.warning(f" CT_Surveillance [ECHEC: {e}]") - # (Je garde les autres sections mais en version compacte) - # ... [toutes les autres sections identiques à la version précédente] ... + # CT_Coface - Code Coface (25 caractères max) + if client_data.get("coface"): + coface = clean_str(client_data["coface"], 25) + try: + client.CT_Coface = coface + logger.info(f" CT_Coface = {coface} [OK]") + except Exception as e: + logger.warning(f" CT_Coface [ECHEC: {e}]") + # Autres champs de surveillance + if client_data.get("forme_juridique"): + try_set_attribute(client, "CT_SvFormeJuri", clean_str(client_data["forme_juridique"], 33)) + + if client_data.get("effectif"): + try_set_attribute(client, "CT_SvEffectif", clean_str(client_data["effectif"], 11)) + + if client_data.get("sv_regularite"): + try_set_attribute(client, "CT_SvRegul", clean_str(client_data["sv_regularite"], 3)) + + if client_data.get("sv_cotation"): + try_set_attribute(client, "CT_SvCotation", clean_str(client_data["sv_cotation"], 5)) + + if client_data.get("sv_objet_maj"): + try_set_attribute(client, "CT_SvObjetMaj", clean_str(client_data["sv_objet_maj"], 61)) + + ca = client_data.get("ca_annuel") or client_data.get("sv_chiffre_affaires") + if ca: + try_set_attribute(client, "CT_SvCA", safe_float(ca)) + + if client_data.get("sv_resultat"): + try_set_attribute(client, "CT_SvResultat", safe_float(client_data["sv_resultat"])) + # ============================================================ # DIAGNOSTIC PRE-WRITE # ============================================================ - logger.info("=" * 60) - logger.info("[?] DIAGNOSTIC PRE-WRITE") + logger.info("=" * 80) + logger.info("[DIAGNOSTIC PRE-WRITE]") - diagnostic_fields = ["CT_Intitule", "CT_Num", "CT_Qualite", "CompteGPrinc"] - - for champ in diagnostic_fields: + champs_diagnostic = [ + 'CT_Num', 'CT_Intitule', 'CT_Type', 'CT_Qualite', + 'CT_Facture', 'CT_Lettrage', 'CT_Sommeil', + ] + + for champ in champs_diagnostic: try: - if champ == "CompteGPrinc": - val = "Défini" if hasattr(client, champ) else None - else: - val = getattr(client, champ, None) - - status = "[OK]" if val is not None else "[!!]" - logger.info(f" {status} {champ}: {val}") + valeur = getattr(client, champ, "ATTRIBUT_INEXISTANT") + logger.info(f" {champ}: {valeur}") except Exception as e: - logger.error(f" [ERR] {champ}: {e}") - + logger.info(f" {champ}: ERREUR ({str(e)[:50]})") + try: - ct_type_actual = getattr(client, "CT_Type", None) - logger.info(f" [OK] CT_Type (auto): {ct_type_actual}") - except: - pass + compte_obj = client.CompteGPrinc + if compte_obj: + logger.info(f" CompteGPrinc.CG_Num: {compte_obj.CG_Num}") + logger.info(f" CompteGPrinc.CG_Type: {compte_obj.CG_Type}") + else: + logger.error(" CompteGPrinc: NULL !!!") + except Exception as e: + logger.error(f" CompteGPrinc: ERREUR - {e}") + + logger.info("=" * 80) # ============================================================ - # ECRITURE + # WRITE # ============================================================ - logger.info("=" * 60) - logger.info("[>] Ecriture dans Sage (Write)...") + logger.info("[WRITE]") try: client.Write() - logger.info("[OK] Write() reussi") + client.Read() + logger.info("[OK] Write reussi") except Exception as e: error_detail = str(e) try: @@ -5797,49 +5693,32 @@ class SageConnector: error_detail = f"{sage_error.Description} (Code: {sage_error.Number})" except: pass - logger.error(f"[!!] Echec Write(): {error_detail}") + + logger.error(f"[ERREUR] {error_detail}") raise RuntimeError(f"Echec Write(): {error_detail}") - # ============================================================ - # RELECTURE - # ============================================================ - try: - client.Read() - except: - pass - num_final = getattr(client, "CT_Num", numero) - type_tiers_final = getattr(client, "CT_Type", type_tiers) - logger.info("=" * 60) - logger.info(f"[OK] TIERS CREE: {num_final} (Type: {type_tiers_final})") - logger.info("=" * 60) + logger.info("=" * 80) + logger.info(f"[SUCCES] CLIENT CREE: {num_final}") + logger.info("=" * 80) return { "numero": num_final, "intitule": intitule, - "type_tiers": type_tiers_final, + "type_tiers": type_tiers, "qualite": qualite, "compte_general": compte, "date_creation": datetime.now().isoformat(), } except ValueError as e: - logger.error(f"[!!] Erreur validation: {e}") + logger.error(f"[ERREUR VALIDATION] {e}") raise except Exception as e: - logger.error(f"[!!] Erreur creation: {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: - pass - raise RuntimeError(f"Erreur technique Sage: {error_message}") - - + logger.error(f"[ERREUR] {e}", exc_info=True) + raise RuntimeError(f"Erreur technique: {e}") + def modifier_client(self, code: str, client_data: Dict) -> Dict: if not self.cial: raise RuntimeError("Connexion Sage non établie") diff --git a/test.py b/test.py index d634775..18a7022 100644 --- a/test.py +++ b/test.py @@ -1,316 +1,323 @@ """ -🧪 TEST GÉNÉRATION PDF AVEC CRYSTAL REPORTS -À lancer immédiatement après installation Crystal +🔬 DIAGNOSTIC APPROFONDI CRYSTAL REPORTS +Découvre pourquoi Crystal ne fonctionne pas après redémarrage """ -import win32com.client import os -import time -import logging +import winreg +import subprocess +import sys -logger = logging.getLogger(__name__) - - -def test_crystal_pdf_simple(): - """ - Test rapide de Crystal Reports - sans Sage - Vérifie juste que Crystal peut s'instancier et exporter - """ - print("\n" + "="*70) - print("🧪 TEST CRYSTAL REPORTS - INSTANCIATION SIMPLE") +def diagnostic_complet_crystal(): + """Diagnostic exhaustif de l'installation Crystal""" + + print("="*70) + print("🔬 DIAGNOSTIC APPROFONDI CRYSTAL REPORTS") print("="*70) - try: - # 1. Instancier Crystal - print("\n1. Instanciation Crystal Runtime...") - - prog_ids = [ - "CrystalRuntime.Application.140", - "CrystalRuntime.Application.13", - "CrystalRuntime.Application.12", - ] - - crystal = None - prog_id_utilisee = None - - for prog_id in prog_ids: + problemes = [] + solutions = [] + + # ========================================== + # 1. VÉRIFIER SI CRYSTAL EST INSTALLÉ + # ========================================== + print("\n📁 1. Vérification présence des fichiers...") + + chemins_installation = [ + r"C:\Program Files\SAP BusinessObjects", + r"C:\Program Files (x86)\SAP BusinessObjects", + r"C:\Program Files\SAP\Crystal Reports", + r"C:\Program Files (x86)\SAP\Crystal Reports", + ] + + crystal_trouve = False + chemin_crystal = None + + for chemin in chemins_installation: + if os.path.exists(chemin): + print(f" ✅ Dossier trouvé : {chemin}") + crystal_trouve = True + chemin_crystal = chemin + + # Afficher taille try: - crystal = win32com.client.Dispatch(prog_id) - prog_id_utilisee = prog_id - print(f" ✅ {prog_id}") - break + total_size = 0 + for dirpath, dirnames, filenames in os.walk(chemin): + for f in filenames: + fp = os.path.join(dirpath, f) + try: + total_size += os.path.getsize(fp) + except: + pass + + size_mb = total_size / (1024 * 1024) + print(f" Taille : {size_mb:.1f} MB") + + if size_mb < 100: + print(f" ⚠️ Taille suspecte (attendu: 300-800 MB)") + problemes.append("Installation incomplète (taille trop petite)") + except Exception as e: - print(f" ❌ {prog_id}: {e}") - - if not crystal: - print("\n❌ ÉCHEC : Aucune version de Crystal disponible") - print(" → Vérifier installation") - print(" → Redémarrer le serveur si juste installé") - return False - - print(f"\n✅ Crystal opérationnel : {prog_id_utilisee}") - - # 2. Vérifier méthodes disponibles - print("\n2. Vérification méthodes d'export...") - - methodes_requises = ['OpenReport', 'Export'] - - for methode in methodes_requises: - if hasattr(crystal, methode): - print(f" ✅ {methode}()") - else: - print(f" ❌ {methode}() manquante") - - print("\n✅ Crystal Reports est prêt pour générer des PDF !") - return True - - except Exception as e: - print(f"\n❌ ERREUR : {e}") - return False - - -def tester_pdf_avec_sage(sage_connector, numero: str, type_doc: int): - """ - Test de génération PDF via Sage avec Crystal + print(f" ⚠️ Impossible de calculer taille : {e}") + else: + print(f" ❌ Absent : {chemin}") - Args: - sage_connector: Instance de ton SageConnector - numero: Numéro document (ex: "FA00123") - type_doc: Type document (0=devis, 60=facture, etc.) + if not crystal_trouve: + print("\n❌ PROBLÈME MAJEUR : Crystal Reports n'est pas installé") + problemes.append("Crystal Reports non installé") + solutions.append("Télécharger et installer SAP Crystal Reports Runtime") + return {"problemes": problemes, "solutions": solutions, "installe": False} - Returns: - tuple: (success: bool, pdf_bytes: bytes, message: str) - """ - print("\n" + "="*70) - print(f"🧪 TEST GÉNÉRATION PDF SAGE + CRYSTAL") - print(f" Document: {numero} (type={type_doc})") - print("="*70) + # ========================================== + # 2. CHERCHER LES DLL CRITIQUES + # ========================================== + print("\n📦 2. Recherche DLL critiques...") + + dll_critiques = { + 'crpe32.dll': 'Crystal Reports Print Engine (CRITIQUE)', + 'crxf_pdf.dll': 'Export PDF (CRITIQUE)', + 'crdb_adoplus.dll': 'Connexion base de données', + 'CrystalDecisions.CrystalReports.Engine.dll': 'Moteur Crystal .NET', + } + + dll_trouvees = {} + + for dll_nom, description in dll_critiques.items(): + trouve = False + + for root, dirs, files in os.walk(chemin_crystal): + if dll_nom.lower() in [f.lower() for f in files]: + dll_path = os.path.join(root, dll_nom) + dll_trouvees[dll_nom] = dll_path + print(f" ✅ {dll_nom}") + print(f" {dll_path}") + trouve = True + break + + if not trouve: + print(f" ❌ {dll_nom} - {description}") + if "CRITIQUE" in description: + problemes.append(f"{dll_nom} manquante") + + if len(dll_trouvees) < 2: + print("\n ⚠️ Trop peu de DLL trouvées - Installation corrompue") + problemes.append("DLL manquantes - Installation corrompue") + solutions.append("Réinstaller Crystal Reports Runtime") + + # ========================================== + # 3. VÉRIFIER LE REGISTRE + # ========================================== + print("\n📋 3. Vérification registre Windows...") + + prog_ids = [ + "CrystalRuntime.Application.140", + "CrystalRuntime.Application.13", + "CrystalRuntime.Application.12", + "CrystalRuntime.Application", + "CrystalDesignRunTime.Application", + ] + + prog_ids_trouves = [] + + for prog_id in prog_ids: + try: + # Vérifier existence + key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, prog_id) + print(f" ✅ {prog_id}") + + # Lire le CLSID + try: + clsid_key = winreg.OpenKey(key, "CLSID") + clsid, _ = winreg.QueryValueEx(clsid_key, "") + print(f" CLSID: {clsid}") + + # Vérifier que le CLSID existe aussi + try: + clsid_path = f"CLSID\\{clsid}" + clsid_key_check = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, clsid_path) + + # Lire InprocServer32 (chemin DLL) + try: + server_key = winreg.OpenKey(clsid_key_check, "InprocServer32") + dll_path, _ = winreg.QueryValueEx(server_key, "") + print(f" DLL: {dll_path}") + + # Vérifier que la DLL existe + if not os.path.exists(dll_path): + print(f" ❌ DLL INTROUVABLE: {dll_path}") + problemes.append(f"{prog_id}: DLL manquante ({dll_path})") + else: + prog_ids_trouves.append(prog_id) + + except: + print(f" ⚠️ InprocServer32 non trouvé") + + except: + print(f" ❌ CLSID {clsid} non trouvé dans registre") + problemes.append(f"{prog_id}: CLSID cassé") + + except: + print(f" ⚠️ Pas de CLSID") + + winreg.CloseKey(key) + + except: + print(f" ❌ {prog_id}") + + if not prog_ids_trouves: + print("\n ⚠️ Aucun ProgID valide - Enregistrement COM échoué") + problemes.append("ProgID non enregistrés correctement") + solutions.append("Réenregistrer les DLL Crystal avec regsvr32") + + # ========================================== + # 4. VÉRIFIER ARCHITECTURE (32 vs 64 bit) + # ========================================== + print("\n🔧 4. Vérification architecture...") + + # Architecture Python + python_arch = "64-bit" if sys.maxsize > 2**32 else "32-bit" + print(f" Python : {python_arch}") + + # Architecture système + import platform + sys_arch = platform.machine() + print(f" Système : {sys_arch}") + + # Détecter architecture Crystal installée + crystal_arch = None + if dll_trouvees: + first_dll = list(dll_trouvees.values())[0] + if "win64_x64" in first_dll or "x64" in first_dll: + crystal_arch = "64-bit" + elif "win32_x86" in first_dll or "x86" in first_dll: + crystal_arch = "32-bit" + + if crystal_arch: + print(f" Crystal : {crystal_arch}") + + if python_arch != crystal_arch: + print(f"\n ❌ INCOMPATIBILITÉ ARCHITECTURE") + print(f" Python {python_arch} ne peut pas utiliser Crystal {crystal_arch}") + problemes.append(f"Incompatibilité: Python {python_arch} vs Crystal {crystal_arch}") + solutions.append(f"Réinstaller Crystal en version {python_arch}") + + # ========================================== + # 5. VÉRIFIER SERVICES WINDOWS + # ========================================== + print("\n🔄 5. Vérification services Windows...") try: - # 1. Vérifier connexion Sage - if not sage_connector.cial: - return False, None, "Connexion Sage non établie" - - print("\n1. ✅ Connexion Sage OK") - - # 2. Vérifier Crystal - print("\n2. Vérification Crystal...") - - prog_ids = [ - "CrystalRuntime.Application.140", - "CrystalRuntime.Application.13", - "CrystalRuntime.Application.12", - ] - - crystal_ok = False - for prog_id in prog_ids: - try: - test = win32com.client.Dispatch(prog_id) - crystal_ok = True - print(f" ✅ Crystal disponible : {prog_id}") - break - except: - pass - - if not crystal_ok: - return False, None, "Crystal Reports non disponible - Redémarrer le serveur" - - # 3. Chercher modèle .BGC - print("\n3. Recherche modèle Crystal...") - - modeles = sage_connector.lister_modeles_crystal() - - # Déterminer catégorie - mapping = {0: "devis", 10: "commandes", 30: "livraisons", 60: "factures", 50: "avoirs"} - categorie = mapping.get(type_doc, "autres") - - if categorie not in modeles or not modeles[categorie]: - return False, None, f"Aucun modèle trouvé pour type {type_doc}" - - modele = modeles[categorie][0] - chemin_modele = modele["chemin_complet"] - - print(f" ✅ Modèle : {modele['fichier']}") - print(f" 📁 Chemin : {chemin_modele}") - - if not os.path.exists(chemin_modele): - return False, None, f"Fichier modèle introuvable : {chemin_modele}" - - # 4. Générer PDF - print("\n4. Génération PDF...") - print(" ⏳ Traitement en cours...") - - start_time = time.time() - - try: - # Utiliser ta méthode existante - pdf_bytes = sage_connector.generer_pdf_document( - numero=numero, - type_doc=type_doc, - modele=modele["fichier"] - ) - - elapsed = time.time() - start_time - - if pdf_bytes and len(pdf_bytes) > 500: - print(f"\n✅ PDF GÉNÉRÉ AVEC SUCCÈS !") - print(f" 📊 Taille : {len(pdf_bytes):,} octets") - print(f" ⏱️ Temps : {elapsed:.2f}s") - - return True, pdf_bytes, "Succès" - else: - return False, None, "PDF généré mais vide ou corrompu" - - except Exception as e: - elapsed = time.time() - start_time - print(f"\n❌ ÉCHEC après {elapsed:.2f}s") - print(f" Erreur : {e}") - - return False, None, str(e) - - except Exception as e: - logger.error(f"Erreur test PDF : {e}", exc_info=True) - return False, None, str(e) - - -def analyser_erreur_generation(erreur_msg: str): - """ - Analyse une erreur de génération PDF et propose solutions - """ - print("\n" + "="*70) - print("🔍 ANALYSE DE L'ERREUR") - print("="*70) - - erreur_lower = erreur_msg.lower() - - if "access" in erreur_lower or "permission" in erreur_lower: - print(""" -❌ PROBLÈME : Permissions fichiers - -💡 SOLUTIONS : - 1. Vérifier que le dossier temporaire est accessible : - icacls "C:\\Windows\\Temp" /grant Everyone:(OI)(CI)F /T - - 2. Vérifier permissions modèles .BGC : - icacls "C:\\Users\\Public\\Documents\\Sage" /grant Everyone:(OI)(CI)R /T - - 3. Exécuter l'application avec droits admin (temporairement) - """) - - elif "not found" in erreur_lower or "introuvable" in erreur_lower: - print(""" -❌ PROBLÈME : Fichier modèle introuvable - -💡 SOLUTIONS : - 1. Vérifier que le modèle .BGC existe : - dir "C:\\Users\\Public\\Documents\\Sage\\Entreprise 100c\\*.bgc" /s - - 2. Spécifier le chemin complet du modèle - - 3. Utiliser un modèle par défaut Sage - """) - - elif "com" in erreur_lower or "dispatch" in erreur_lower: - print(""" -❌ PROBLÈME : Erreur COM / Crystal Runtime - -💡 SOLUTIONS : - 1. REDÉMARRER LE SERVEUR (recommandé) - shutdown /r /t 60 - - 2. Réenregistrer les DLL Crystal : - cd "C:\\Program Files\\SAP BusinessObjects\\Crystal Reports for .NET Framework 4.0\\Common\\SAP BusinessObjects Enterprise XI 4.0\\win64_x64" - regsvr32 /s crpe32.dll - regsvr32 /s crxf_pdf.dll - - 3. Vérifier que les services Crystal sont démarrés : - sc start "SAP Crystal Reports Processing Server" - """) - - elif "database" in erreur_lower or "connexion" in erreur_lower: - print(""" -❌ PROBLÈME : Connexion base de données - -💡 SOLUTIONS : - 1. Le modèle Crystal doit pouvoir se connecter à la base Sage - - 2. Vérifier les paramètres de connexion dans le modèle - - 3. Utiliser l'API Sage pour passer les données au lieu de - connecter Crystal directement à la base - """) - - else: - print(f""" -❌ ERREUR : {erreur_msg} - -💡 SOLUTIONS GÉNÉRIQUES : - 1. Redémarrer le serveur - 2. Vérifier les logs détaillés - 3. Tester avec un modèle Crystal simple - 4. Utiliser PDF custom en attendant - """) - - print("="*70) - - -# Routes API pour tests -def creer_routes_test(app, sage_connector): - """ - Ajouter ces routes dans main.py pour tester facilement - """ - - @app.get("/sage/test-crystal-simple") - async def test_crystal_simple(): - """Test Crystal sans Sage""" - success = test_crystal_pdf_simple() - return { - "success": success, - "message": "Crystal opérationnel" if success else "Crystal non disponible" - } - - @app.get("/sage/test-pdf-complet") - async def test_pdf_complet( - numero: str = "FA00123", - type_doc: int = 60 - ): - """Test génération PDF complète avec Sage + Crystal""" - - success, pdf_bytes, message = tester_pdf_avec_sage( - sage_connector, numero, type_doc + result = subprocess.run( + ['sc', 'query', 'type=', 'service'], + capture_output=True, + text=True, + timeout=10 ) - if success: - from fastapi.responses import Response - return Response( - content=pdf_bytes, - media_type="application/pdf", - headers={ - "Content-Disposition": f"attachment; filename={numero}.pdf" - } - ) - else: - # Analyser erreur - analyser_erreur_generation(message) + services_crystal_attendus = [ + "SAP Crystal Reports", + "Crystal Reports", + "CrystalReports", + ] + + services_trouves = [] + for service in services_crystal_attendus: + if service.lower() in result.stdout.lower(): + services_trouves.append(service) + print(f" ✅ Service trouvé: {service}") + + if not services_trouves: + print(f" ⚠️ Aucun service Crystal trouvé") + print(f" (Normal pour Runtime léger)") + + except Exception as e: + print(f" ⚠️ Impossible de vérifier services: {e}") + + # ========================================== + # 6. TEST INSTANCIATION COM DÉTAILLÉ + # ========================================== + print("\n🧪 6. Test instanciation COM détaillé...") + + import win32com.client + + for prog_id in prog_ids_trouves: + print(f"\n Test: {prog_id}") + try: + obj = win32com.client.Dispatch(prog_id) + print(f" ✅ Instanciation RÉUSSIE") - from fastapi import HTTPException - raise HTTPException( - status_code=500, - detail={ - "error": message, - "recommandation": "Consulter les logs pour analyse détaillée" - } - ) + # Lister méthodes disponibles + print(f" Méthodes disponibles:") + for attr in dir(obj): + if not attr.startswith('_') and callable(getattr(obj, attr, None)): + print(f" - {attr}()") + + return { + "problemes": problemes, + "solutions": solutions, + "installe": True, + "prog_id_fonctionnel": prog_id + } + + except Exception as e: + print(f" ❌ Échec: {e}") + print(f" Type erreur: {type(e).__name__}") + print(f" Code: {e.args if hasattr(e, 'args') else 'N/A'}") + + # ========================================== + # 7. RÉSUMÉ ET RECOMMANDATIONS + # ========================================== + print("\n" + "="*70) + print("📊 RÉSUMÉ DU DIAGNOSTIC") + print("="*70) + + print(f"\n📁 Installation détectée: {'OUI' if crystal_trouve else 'NON'}") + print(f"📦 DLL trouvées: {len(dll_trouvees)}/{len(dll_critiques)}") + print(f"📋 ProgID valides: {len(prog_ids_trouves)}") + print(f"🔧 Architecture: Python {python_arch}, Crystal {crystal_arch or 'INCONNUE'}") + + if problemes: + print(f"\n❌ PROBLÈMES DÉTECTÉS ({len(problemes)}):") + for i, pb in enumerate(problemes, 1): + print(f" {i}. {pb}") + + if solutions: + print(f"\n💡 SOLUTIONS RECOMMANDÉES:") + for i, sol in enumerate(solutions, 1): + print(f" {i}. {sol}") + + print("\n" + "="*70) + + return { + "problemes": problemes, + "solutions": solutions, + "installe": crystal_trouve, + "dll_trouvees": list(dll_trouvees.keys()), + "prog_ids_valides": prog_ids_trouves, + "architecture_ok": python_arch == crystal_arch if crystal_arch else False + } if __name__ == "__main__": - print("🚀 LANCEMENT TESTS CRYSTAL REPORTS") + resultats = diagnostic_complet_crystal() - # Test 1 : Crystal seul - crystal_ok = test_crystal_pdf_simple() + print("\n💾 Résultats sauvegardés dans diagnostic_crystal.txt") - if not crystal_ok: - print("\n⚠️ Crystal non opérationnel") - print(" → Redémarrer le serveur et relancer") - else: - print("\n✅ Crystal OK - Prêt pour génération PDF avec Sage") \ No newline at end of file + # Sauvegarder dans fichier + with open("diagnostic_crystal.txt", "w", encoding="utf-8") as f: + f.write("="*70 + "\n") + f.write("DIAGNOSTIC CRYSTAL REPORTS\n") + f.write("="*70 + "\n\n") + + f.write(f"Installation détectée: {resultats['installe']}\n") + f.write(f"DLL trouvées: {', '.join(resultats.get('dll_trouvees', []))}\n") + f.write(f"ProgID valides: {', '.join(resultats.get('prog_ids_valides', []))}\n") + f.write(f"Architecture OK: {resultats.get('architecture_ok', False)}\n\n") + + f.write("PROBLÈMES:\n") + for pb in resultats['problemes']: + f.write(f" - {pb}\n") + + f.write("\nSOLUTIONS:\n") + for sol in resultats['solutions']: + f.write(f" - {sol}\n") \ No newline at end of file