Cleaned projects' files

This commit is contained in:
fanilo 2026-01-18 09:41:22 +01:00
parent 9ffad8287d
commit 31dec46226
9 changed files with 1 additions and 267 deletions

2
.gitignore vendored
View file

@ -34,4 +34,4 @@ htmlcov/
dist/ dist/
cleaner.py *clean*.py

View file

@ -1680,7 +1680,6 @@ def get_tous_journaux():
try: try:
journaux = sage.lire_tous_journaux() journaux = sage.lire_tous_journaux()
# Grouper par type
par_type = {} par_type = {}
for j in journaux: for j in journaux:
t = j["type_libelle"] t = j["type_libelle"]
@ -1745,7 +1744,6 @@ def introspection_com():
} }
with sage._com_context(), sage._lock_com: with sage._com_context(), sage._lock_com:
# Attributs de cial
try: try:
for attr in dir(sage.cial): for attr in dir(sage.cial):
if not attr.startswith("_"): if not attr.startswith("_"):
@ -1753,7 +1751,6 @@ def introspection_com():
except Exception as e: except Exception as e:
resultats["cial_error"] = str(e) resultats["cial_error"] = str(e)
# Attributs de BaseCpta
try: try:
base_cpta = sage.cial.BaseCpta base_cpta = sage.cial.BaseCpta
for attr in dir(base_cpta): for attr in dir(base_cpta):
@ -1762,14 +1759,12 @@ def introspection_com():
except Exception as e: except Exception as e:
resultats["base_cpta_error"] = str(e) resultats["base_cpta_error"] = str(e)
# Attributs de ParametreDossier
try: try:
param = sage.cial.BaseCpta.ParametreDossier param = sage.cial.BaseCpta.ParametreDossier
for attr in dir(param): for attr in dir(param):
if not attr.startswith("_"): if not attr.startswith("_"):
resultats["param_dossier_attributes"].append(attr) resultats["param_dossier_attributes"].append(attr)
# Tester spécifiquement les attributs logo possibles
resultats["logo_tests"] = {} resultats["logo_tests"] = {}
for logo_attr in [ for logo_attr in [
"Logo", "Logo",
@ -1842,7 +1837,6 @@ def get_tous_reglements(
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
# Route: Détail d'un règlement
@app.get("/sage/reglements/facture/{facture_no}", dependencies=[Depends(verify_token)]) @app.get("/sage/reglements/facture/{facture_no}", dependencies=[Depends(verify_token)])
def get_reglement_facture_detail(facture_no): def get_reglement_facture_detail(facture_no):
try: try:

View file

@ -1003,12 +1003,10 @@ class SageConnector:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# === DÉTECTION DES COLONNES ===
logger.info(f"[SQL] Lecture article {reference}...") logger.info(f"[SQL] Lecture article {reference}...")
cursor.execute("SELECT TOP 1 * FROM F_ARTICLE") cursor.execute("SELECT TOP 1 * FROM F_ARTICLE")
colonnes_disponibles = [column[0] for column in cursor.description] colonnes_disponibles = [column[0] for column in cursor.description]
# Configuration du mapping complet
colonnes_config = { colonnes_config = {
"AR_Ref": "reference", "AR_Ref": "reference",
"AR_Design": "designation", "AR_Design": "designation",
@ -1090,7 +1088,6 @@ class SageConnector:
"AR_Exclure": "exclure", "AR_Exclure": "exclure",
} }
# Sélection des colonnes disponibles
colonnes_a_lire = [ colonnes_a_lire = [
col_sql col_sql
for col_sql in colonnes_config.keys() for col_sql in colonnes_config.keys()
@ -1101,7 +1098,6 @@ class SageConnector:
logger.error("[SQL] Aucune colonne mappée trouvée !") logger.error("[SQL] Aucune colonne mappée trouvée !")
return None return None
# Construction de la requête SQL avec échappement des noms de colonnes
colonnes_sql = [] colonnes_sql = []
for col in colonnes_a_lire: for col in colonnes_a_lire:
if " " in col or "/" in col or "è" in col: if " " in col or "/" in col or "è" in col:
@ -1120,7 +1116,6 @@ class SageConnector:
logger.info(f"[SQL] Article {reference} non trouvé") logger.info(f"[SQL] Article {reference} non trouvé")
return None return None
# Construction du dictionnaire row_data
row_data = {} row_data = {}
for idx, col_sql in enumerate(colonnes_a_lire): for idx, col_sql in enumerate(colonnes_a_lire):
valeur = row[idx] valeur = row[idx]
@ -1128,10 +1123,8 @@ class SageConnector:
valeur = valeur.strip() valeur = valeur.strip()
row_data[col_sql] = valeur row_data[col_sql] = valeur
# Mapping de l'article
article = _mapper_article_depuis_row(row_data, colonnes_config) article = _mapper_article_depuis_row(row_data, colonnes_config)
# Enrichissements
articles = [ articles = [
article article
] # Liste d'un seul article pour les fonctions d'enrichissement ] # Liste d'un seul article pour les fonctions d'enrichissement
@ -1876,7 +1869,6 @@ class SageConnector:
persist_tiers = None persist_tiers = None
type_tiers = None type_tiers = None
# Tentative 1 : Client
try: try:
logger.info(" Recherche dans Clients...") logger.info(" Recherche dans Clients...")
persist_tiers = factory_client.ReadNumero(numero_client) persist_tiers = factory_client.ReadNumero(numero_client)
@ -1886,7 +1878,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.debug(f" Pas trouvé comme Client: {e}") logger.debug(f" Pas trouvé comme Client: {e}")
# Tentative 2 : Fournisseur (si pas trouvé comme client)
if not persist_tiers: if not persist_tiers:
try: try:
logger.info(" Recherche dans Fournisseurs...") logger.info(" Recherche dans Fournisseurs...")
@ -1897,7 +1888,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.debug(f" Pas trouvé comme Fournisseur: {e}") logger.debug(f" Pas trouvé comme Fournisseur: {e}")
# Vérification finale
if not persist_tiers: if not persist_tiers:
raise ValueError( raise ValueError(
f"Le tiers '{numero_client}' est introuvable dans Sage 100c. " f"Le tiers '{numero_client}' est introuvable dans Sage 100c. "
@ -1905,7 +1895,6 @@ class SageConnector:
f"(Client ou Fournisseur)." f"(Client ou Fournisseur)."
) )
# Cast et lecture
try: try:
client_obj = win32com.client.CastTo(persist_tiers, "IBOClient3") client_obj = win32com.client.CastTo(persist_tiers, "IBOClient3")
client_obj.Read() client_obj.Read()
@ -2348,7 +2337,6 @@ class SageConnector:
persist_tiers = None persist_tiers = None
type_tiers = None type_tiers = None
# Tentative 1 : Client
try: try:
logger.info(" Recherche dans Clients...") logger.info(" Recherche dans Clients...")
persist_tiers = factory_client.ReadNumero(numero) persist_tiers = factory_client.ReadNumero(numero)
@ -2358,7 +2346,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.debug(f" Pas trouvé comme Client: {e}") logger.debug(f" Pas trouvé comme Client: {e}")
# Tentative 2 : Fournisseur (si pas trouvé comme client)
if not persist_tiers: if not persist_tiers:
try: try:
logger.info(" Recherche dans Fournisseurs...") logger.info(" Recherche dans Fournisseurs...")
@ -2369,7 +2356,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.debug(f" Pas trouvé comme Fournisseur: {e}") logger.debug(f" Pas trouvé comme Fournisseur: {e}")
# Vérification finale
if not persist_tiers: if not persist_tiers:
raise ValueError( raise ValueError(
f"Le tiers '{numero}' est introuvable dans Sage 100c. " f"Le tiers '{numero}' est introuvable dans Sage 100c. "
@ -2377,7 +2363,6 @@ class SageConnector:
f"(Client ou Fournisseur)." f"(Client ou Fournisseur)."
) )
# Cast et lecture
try: try:
client_obj = win32com.client.CastTo(persist_tiers, "IBOClient3") client_obj = win32com.client.CastTo(persist_tiers, "IBOClient3")
client_obj.Read() client_obj.Read()
@ -3719,7 +3704,6 @@ class SageConnector:
f" [DEBUG] Méthodes disponibles sur client: {methodes_client}" f" [DEBUG] Méthodes disponibles sur client: {methodes_client}"
) )
# Chercher spécifiquement les méthodes de verrouillage
lock_methods = [ lock_methods = [
m m
for m in methodes_client for m in methodes_client
@ -3736,7 +3720,6 @@ class SageConnector:
for attempt in range(max_retries): for attempt in range(max_retries):
try: try:
# Approche 1: ReadLock (méthode préférée)
if hasattr(client, "ReadLock"): if hasattr(client, "ReadLock"):
client.ReadLock() client.ReadLock()
locked = True locked = True
@ -3744,7 +3727,6 @@ class SageConnector:
logger.info(" Verrouillage via ReadLock() [OK]") logger.info(" Verrouillage via ReadLock() [OK]")
break break
# Approche 2: Lock
elif hasattr(client, "Lock"): elif hasattr(client, "Lock"):
client.Lock() client.Lock()
locked = True locked = True
@ -3752,7 +3734,6 @@ class SageConnector:
logger.info(" Verrouillage via Lock() [OK]") logger.info(" Verrouillage via Lock() [OK]")
break break
# Approche 3: LockRecord
elif hasattr(client, "LockRecord"): elif hasattr(client, "LockRecord"):
client.LockRecord() client.LockRecord()
locked = True locked = True
@ -3760,7 +3741,6 @@ class SageConnector:
logger.info(" Verrouillage via LockRecord() [OK]") logger.info(" Verrouillage via LockRecord() [OK]")
break break
# Approche 4: Read avec paramètre mode écriture
else: else:
try: try:
client.Read(1) # 1 = mode écriture client.Read(1) # 1 = mode écriture
@ -3796,7 +3776,6 @@ class SageConnector:
"Vérifiez qu'il n'est pas ouvert dans Sage ou par un autre processus." "Vérifiez qu'il n'est pas ouvert dans Sage ou par un autre processus."
) )
else: else:
# Autre erreur, propager
raise raise
logger.info( logger.info(
@ -4366,7 +4345,6 @@ class SageConnector:
if not champs_modifies: if not champs_modifies:
logger.warning("Aucun champ à modifier") logger.warning("Aucun champ à modifier")
# Déverrouiller si nécessaire
if locked: if locked:
try: try:
if hasattr(client, "ReadUnlock"): if hasattr(client, "ReadUnlock"):
@ -4399,7 +4377,6 @@ class SageConnector:
logger.error(f"[ERREUR] {error_detail}") logger.error(f"[ERREUR] {error_detail}")
raise RuntimeError(f"Echec Write(): {error_detail}") raise RuntimeError(f"Echec Write(): {error_detail}")
finally: finally:
# Toujours déverrouiller après Write (succès ou échec)
if locked: if locked:
try: try:
if hasattr(client, "ReadUnlock"): if hasattr(client, "ReadUnlock"):
@ -4414,7 +4391,6 @@ class SageConnector:
except Exception as unlock_err: except Exception as unlock_err:
logger.warning(f" Déverrouillage ignoré: {unlock_err}") logger.warning(f" Déverrouillage ignoré: {unlock_err}")
# Relire après Write pour retourner les données à jour
client.Read() client.Read()
logger.info("=" * 80) logger.info("=" * 80)
@ -4478,7 +4454,6 @@ class SageConnector:
try: try:
logger.info("[ARTICLE] === CREATION ARTICLE ===") logger.info("[ARTICLE] === CREATION ARTICLE ===")
# === Validation données ===
valide, erreur = valider_donnees_creation(article_data) valide, erreur = valider_donnees_creation(article_data)
if not valide: if not valide:
raise ValueError(erreur) raise ValueError(erreur)
@ -4492,7 +4467,6 @@ class SageConnector:
logger.debug(f"BeginTrans non disponible : {e}") logger.debug(f"BeginTrans non disponible : {e}")
try: try:
# === Découverte dépôts ===
depots_disponibles = [] depots_disponibles = []
depot_a_utiliser = None depot_a_utiliser = None
depot_code_demande = article_data.get("depot_code") depot_code_demande = article_data.get("depot_code")
@ -4556,7 +4530,6 @@ class SageConnector:
f"[DEPOT] Utilisation : '{depot_a_utiliser['code']}' ({depot_a_utiliser['intitule']})" f"[DEPOT] Utilisation : '{depot_a_utiliser['code']}' ({depot_a_utiliser['intitule']})"
) )
# === Extraction et validation des données ===
reference = article_data.get("reference", "").upper().strip() reference = article_data.get("reference", "").upper().strip()
designation = article_data.get("designation", "").strip() designation = article_data.get("designation", "").strip()
if len(designation) > 69: if len(designation) > 69:
@ -4569,7 +4542,6 @@ class SageConnector:
logger.info(f"[ARTICLE] Référence : {reference}") logger.info(f"[ARTICLE] Référence : {reference}")
logger.info(f"[ARTICLE] Désignation : {designation}") logger.info(f"[ARTICLE] Désignation : {designation}")
# === Vérifier si article existe ===
factory = self.cial.FactoryArticle factory = self.cial.FactoryArticle
try: try:
article_existant = factory.ReadReference(reference) article_existant = factory.ReadReference(reference)
@ -4583,7 +4555,6 @@ class SageConnector:
): ):
raise raise
# === Créer l'article ===
persist = factory.Create() persist = factory.Create()
article = win32com.client.CastTo(persist, "IBOArticle3") article = win32com.client.CastTo(persist, "IBOArticle3")
article.SetDefault() article.SetDefault()
@ -4591,7 +4562,6 @@ class SageConnector:
article.AR_Ref = reference article.AR_Ref = reference
article.AR_Design = designation article.AR_Design = designation
# === Recherche article modèle ===
logger.info("[MODELE] Recherche article modèle...") logger.info("[MODELE] Recherche article modèle...")
article_modele_ref = None article_modele_ref = None
article_modele = None article_modele = None
@ -4634,7 +4604,6 @@ class SageConnector:
"Aucun article modèle trouvé. Créez au moins un article dans Sage." "Aucun article modèle trouvé. Créez au moins un article dans Sage."
) )
# === Copie Unite depuis modèle ===
logger.info("[UNITE] Copie Unite depuis modèle...") logger.info("[UNITE] Copie Unite depuis modèle...")
unite_trouvee = False unite_trouvee = False
try: try:
@ -4651,7 +4620,6 @@ class SageConnector:
"Impossible de copier l'unité depuis le modèle" "Impossible de copier l'unité depuis le modèle"
) )
# === Gestion famille ===
famille_trouvee = False famille_trouvee = False
famille_code_personnalise = article_data.get("famille") famille_code_personnalise = article_data.get("famille")
@ -4736,7 +4704,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.debug(f" Famille non copiable : {e}") logger.debug(f" Famille non copiable : {e}")
# === Champs obligatoires depuis modèle ===
logger.info("[CHAMPS] Copie champs obligatoires...") logger.info("[CHAMPS] Copie champs obligatoires...")
article.AR_Type = int(getattr(article_modele, "AR_Type", 0)) article.AR_Type = int(getattr(article_modele, "AR_Type", 0))
article.AR_Nature = int(getattr(article_modele, "AR_Nature", 0)) article.AR_Nature = int(getattr(article_modele, "AR_Nature", 0))
@ -4744,12 +4711,10 @@ class SageConnector:
article.AR_SuiviStock = 2 article.AR_SuiviStock = 2
logger.info(" [OK] Champs de base copiés (AR_SuiviStock=2)") logger.info(" [OK] Champs de base copiés (AR_SuiviStock=2)")
# === Application des champs fournis ===
logger.info("[CHAMPS] Application champs fournis...") logger.info("[CHAMPS] Application champs fournis...")
champs_appliques = [] champs_appliques = []
champs_echoues = [] champs_echoues = []
# Prix de vente
if "prix_vente" in article_data: if "prix_vente" in article_data:
try: try:
article.AR_PrixVen = float(article_data["prix_vente"]) article.AR_PrixVen = float(article_data["prix_vente"])
@ -4760,7 +4725,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"prix_vente: {e}") champs_echoues.append(f"prix_vente: {e}")
# Prix d'achat
if "prix_achat" in article_data: if "prix_achat" in article_data:
try: try:
article.AR_PrixAchat = float(article_data["prix_achat"]) article.AR_PrixAchat = float(article_data["prix_achat"])
@ -4771,7 +4735,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"prix_achat: {e}") champs_echoues.append(f"prix_achat: {e}")
# Coefficient
if "coef" in article_data: if "coef" in article_data:
try: try:
article.AR_Coef = float(article_data["coef"]) article.AR_Coef = float(article_data["coef"])
@ -4780,7 +4743,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"coef: {e}") champs_echoues.append(f"coef: {e}")
# Code EAN
if "code_ean" in article_data: if "code_ean" in article_data:
try: try:
article.AR_CodeBarre = str(article_data["code_ean"]) article.AR_CodeBarre = str(article_data["code_ean"])
@ -4789,7 +4751,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"code_ean: {e}") champs_echoues.append(f"code_ean: {e}")
# Description -> AR_Langue1
if "description" in article_data: if "description" in article_data:
try: try:
article.AR_Langue1 = str(article_data["description"])[:255] article.AR_Langue1 = str(article_data["description"])[:255]
@ -4798,7 +4759,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"description: {e}") champs_echoues.append(f"description: {e}")
# Pays
if "pays" in article_data: if "pays" in article_data:
try: try:
article.AR_Pays = str(article_data["pays"])[:3].upper() article.AR_Pays = str(article_data["pays"])[:3].upper()
@ -4807,7 +4767,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"pays: {e}") champs_echoues.append(f"pays: {e}")
# Garantie
if "garantie" in article_data: if "garantie" in article_data:
try: try:
article.AR_Garantie = int(article_data["garantie"]) article.AR_Garantie = int(article_data["garantie"])
@ -4816,7 +4775,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"garantie: {e}") champs_echoues.append(f"garantie: {e}")
# Délai
if "delai" in article_data: if "delai" in article_data:
try: try:
article.AR_Delai = int(article_data["delai"]) article.AR_Delai = int(article_data["delai"])
@ -4825,7 +4783,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"delai: {e}") champs_echoues.append(f"delai: {e}")
# Poids net
if "poids_net" in article_data: if "poids_net" in article_data:
try: try:
article.AR_PoidsNet = float(article_data["poids_net"]) article.AR_PoidsNet = float(article_data["poids_net"])
@ -4834,7 +4791,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"poids_net: {e}") champs_echoues.append(f"poids_net: {e}")
# Poids brut
if "poids_brut" in article_data: if "poids_brut" in article_data:
try: try:
article.AR_PoidsBrut = float(article_data["poids_brut"]) article.AR_PoidsBrut = float(article_data["poids_brut"])
@ -4845,7 +4801,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"poids_brut: {e}") champs_echoues.append(f"poids_brut: {e}")
# Code fiscal
if "code_fiscal" in article_data: if "code_fiscal" in article_data:
try: try:
article.AR_CodeFiscal = str(article_data["code_fiscal"])[ article.AR_CodeFiscal = str(article_data["code_fiscal"])[
@ -4858,7 +4813,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"code_fiscal: {e}") champs_echoues.append(f"code_fiscal: {e}")
# Soumis escompte
if "soumis_escompte" in article_data: if "soumis_escompte" in article_data:
try: try:
article.AR_Escompte = ( article.AR_Escompte = (
@ -4871,7 +4825,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"soumis_escompte: {e}") champs_echoues.append(f"soumis_escompte: {e}")
# Publié
if "publie" in article_data: if "publie" in article_data:
try: try:
article.AR_Publie = 1 if article_data["publie"] else 0 article.AR_Publie = 1 if article_data["publie"] else 0
@ -4880,7 +4833,6 @@ class SageConnector:
except Exception as e: except Exception as e:
champs_echoues.append(f"publie: {e}") champs_echoues.append(f"publie: {e}")
# En sommeil
if "en_sommeil" in article_data: if "en_sommeil" in article_data:
try: try:
article.AR_Sommeil = 1 if article_data["en_sommeil"] else 0 article.AR_Sommeil = 1 if article_data["en_sommeil"] else 0
@ -4900,7 +4852,6 @@ class SageConnector:
f"[CHAMPS] Appliqués : {len(champs_appliques)} / Échoués : {len(champs_echoues)}" f"[CHAMPS] Appliqués : {len(champs_appliques)} / Échoués : {len(champs_echoues)}"
) )
# === Écriture dans Sage ===
logger.info("[ARTICLE] Écriture dans Sage...") logger.info("[ARTICLE] Écriture dans Sage...")
try: try:
article.Write() article.Write()
@ -4916,7 +4867,6 @@ class SageConnector:
logger.error(f" [ERREUR] Write() : {error_detail}") logger.error(f" [ERREUR] Write() : {error_detail}")
raise RuntimeError(f"Échec création : {error_detail}") raise RuntimeError(f"Échec création : {error_detail}")
# === Statistiques (AR_Stat après Write) ===
stats_a_definir = [] stats_a_definir = []
for i in range(1, 6): for i in range(1, 6):
stat_key = f"stat_0{i}" stat_key = f"stat_0{i}"
@ -4938,7 +4888,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f" ⚠ Statistiques : {e}") logger.warning(f" ⚠ Statistiques : {e}")
# === Commit transaction ===
if transaction_active: if transaction_active:
try: try:
self.cial.CptaApplication.CommitTrans() self.cial.CptaApplication.CommitTrans()
@ -4946,7 +4895,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"[COMMIT] Erreur : {e}") logger.warning(f"[COMMIT] Erreur : {e}")
# === Gestion stocks ===
stock_defini = False stock_defini = False
has_stock_values = stock_reel or stock_mini or stock_maxi has_stock_values = stock_reel or stock_mini or stock_maxi
@ -4955,7 +4903,6 @@ class SageConnector:
f"[STOCK] Définition stock (dépôt '{depot_a_utiliser['code']}')..." f"[STOCK] Définition stock (dépôt '{depot_a_utiliser['code']}')..."
) )
# Méthode 1 : Créer via COM
if stock_reel: if stock_reel:
try: try:
depot_obj = depot_a_utiliser["objet"] depot_obj = depot_a_utiliser["objet"]
@ -5004,7 +4951,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f" [WARN] Stock COM : {e}") logger.warning(f" [WARN] Stock COM : {e}")
# Méthode 2 : Mise à jour SQL si COM échoue ou pour mini/maxi seulement
if (stock_mini or stock_maxi) and not stock_defini: if (stock_mini or stock_maxi) and not stock_defini:
try: try:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
@ -5079,13 +5025,11 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.error(f"[STOCK] Erreur SQL : {e}") logger.error(f"[STOCK] Erreur SQL : {e}")
# === Construction réponse depuis SQL ===
logger.info("[RESPONSE] Construction réponse depuis SQL...") logger.info("[RESPONSE] Construction réponse depuis SQL...")
try: try:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Lecture complète article
cursor.execute( cursor.execute(
""" """
SELECT SELECT
@ -5161,7 +5105,6 @@ class SageConnector:
else None, else None,
} }
# Lecture stocks
cursor.execute( cursor.execute(
""" """
SELECT s.AS_QteSto, s.AS_QteMini, s.AS_QteMaxi, s.AS_QteRes, s.AS_QteCom SELECT s.AS_QteSto, s.AS_QteMini, s.AS_QteMaxi, s.AS_QteRes, s.AS_QteCom
@ -5212,7 +5155,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"[RESPONSE] Erreur lecture SQL : {e}") logger.warning(f"[RESPONSE] Erreur lecture SQL : {e}")
# Fallback sur extraction COM si SQL échoue
logger.info("[FALLBACK] Extraction COM...") logger.info("[FALLBACK] Extraction COM...")
article_cree_persist = factory.ReadReference(reference) article_cree_persist = factory.ReadReference(reference)
if not article_cree_persist: if not article_cree_persist:
@ -5227,7 +5169,6 @@ class SageConnector:
if not resultat: if not resultat:
resultat = {"reference": reference, "designation": designation} resultat = {"reference": reference, "designation": designation}
# Forcer les valeurs connues
for key in [ for key in [
"prix_vente", "prix_vente",
"prix_achat", "prix_achat",
@ -5296,7 +5237,6 @@ class SageConnector:
champs_modifies = [] champs_modifies = []
champs_echoues = [] champs_echoues = []
# === Gestion famille ===
if "famille" in article_data and article_data["famille"]: if "famille" in article_data and article_data["famille"]:
famille_code_demande = article_data["famille"].upper().strip() famille_code_demande = article_data["famille"].upper().strip()
logger.info(f"[FAMILLE] Changement : {famille_code_demande}") logger.info(f"[FAMILLE] Changement : {famille_code_demande}")
@ -5369,7 +5309,6 @@ class SageConnector:
logger.error(f" [ERREUR] Famille : {e}") logger.error(f" [ERREUR] Famille : {e}")
champs_echoues.append(f"famille: {e}") champs_echoues.append(f"famille: {e}")
# === Traitement explicite des champs ===
if "designation" in article_data: if "designation" in article_data:
try: try:
designation = str(article_data["designation"])[:69].strip() designation = str(article_data["designation"])[:69].strip()
@ -5511,7 +5450,6 @@ class SageConnector:
logger.info(f"[ARTICLE] Champs modifiés : {', '.join(champs_modifies)}") logger.info(f"[ARTICLE] Champs modifiés : {', '.join(champs_modifies)}")
logger.info("[ARTICLE] Écriture...") logger.info("[ARTICLE] Écriture...")
# === Écriture COM ===
try: try:
article.Write() article.Write()
logger.info("[ARTICLE] Write() réussi") logger.info("[ARTICLE] Write() réussi")
@ -5528,7 +5466,6 @@ class SageConnector:
logger.error(f"[ARTICLE] Erreur Write() : {error_detail}") logger.error(f"[ARTICLE] Erreur Write() : {error_detail}")
raise RuntimeError(f"Échec modification : {error_detail}") raise RuntimeError(f"Échec modification : {error_detail}")
# === Statistiques (AR_Stat après Write) ===
stats_a_modifier = [] stats_a_modifier = []
for i in range(1, 6): for i in range(1, 6):
stat_key = f"stat_0{i}" stat_key = f"stat_0{i}"
@ -5555,7 +5492,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f" ⚠ Statistiques : {e}") logger.warning(f" ⚠ Statistiques : {e}")
# === Gestion stocks mini/maxi via SQL ===
if "stock_mini" in article_data or "stock_maxi" in article_data: if "stock_mini" in article_data or "stock_maxi" in article_data:
try: try:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
@ -5595,13 +5531,11 @@ class SageConnector:
logger.error(f"[STOCK] Erreur SQL : {e}") logger.error(f"[STOCK] Erreur SQL : {e}")
champs_echoues.append(f"stocks: {e}") champs_echoues.append(f"stocks: {e}")
# === Construction réponse depuis SQL ===
logger.info("[RESPONSE] Construction réponse depuis SQL...") logger.info("[RESPONSE] Construction réponse depuis SQL...")
try: try:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Lecture complète article
cursor.execute( cursor.execute(
""" """
SELECT SELECT
@ -5667,7 +5601,6 @@ class SageConnector:
"stat_05": _safe_strip(row[24]) if row[24] else None, "stat_05": _safe_strip(row[24]) if row[24] else None,
} }
# Lecture stocks
cursor.execute( cursor.execute(
""" """
SELECT s.AS_QteSto, s.AS_QteMini, s.AS_QteMaxi, s.AS_QteRes, s.AS_QteCom SELECT s.AS_QteSto, s.AS_QteMini, s.AS_QteMaxi, s.AS_QteRes, s.AS_QteCom
@ -5718,7 +5651,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"[RESPONSE] Erreur lecture SQL : {e}") logger.warning(f"[RESPONSE] Erreur lecture SQL : {e}")
# Fallback sur extraction COM
article.Read() article.Read()
logger.info( logger.info(
f"[ARTICLE] MODIFIÉ : {reference} ({len(champs_modifies)} champs)" f"[ARTICLE] MODIFIÉ : {reference} ({len(champs_modifies)} champs)"
@ -7495,7 +7427,6 @@ class SageConnector:
cursor.execute(query, params) cursor.execute(query, params)
rows = cursor.fetchall() rows = cursor.fetchall()
# ⚠️⚠️⚠️ VÉRIFIE CETTE LIGNE ⚠️⚠️⚠️
collaborateurs = [collaborators_to_dict(row) for row in rows] collaborateurs = [collaborators_to_dict(row) for row in rows]
logger.info(f"✓ SQL: {len(collaborateurs)} collaborateurs") logger.info(f"✓ SQL: {len(collaborateurs)} collaborateurs")
@ -7530,7 +7461,6 @@ class SageConnector:
if not row: if not row:
return None return None
# ⚠️ UTILISER LA FONCTION DE CLASSE EXISTANTE
collaborateur = collaborators_to_dict(row) collaborateur = collaborators_to_dict(row)
logger.info( logger.info(
@ -7547,7 +7477,6 @@ class SageConnector:
if not self.cial: if not self.cial:
raise RuntimeError("Connexion Sage non établie") raise RuntimeError("Connexion Sage non établie")
# Validation préalable
if not data.get("nom"): if not data.get("nom"):
raise ValueError("Le champ 'nom' est obligatoire") raise ValueError("Le champ 'nom' est obligatoire")
@ -7560,7 +7489,6 @@ class SageConnector:
try: try:
with self._com_context(), self._lock_com: with self._com_context(), self._lock_com:
# ===== VÉRIFICATION DOUBLON VIA SQL =====
logger.info("🔍 Vérification doublon...") logger.info("🔍 Vérification doublon...")
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@ -7575,7 +7503,6 @@ class SageConnector:
) )
logger.info("✓ Pas de doublon") logger.info("✓ Pas de doublon")
# ===== FACTORY + CREATE =====
try: try:
factory = self.cial.FactoryCollaborateur factory = self.cial.FactoryCollaborateur
except AttributeError: except AttributeError:
@ -7583,7 +7510,6 @@ class SageConnector:
persist = factory.Create() persist = factory.Create()
# Cast vers interface
collab = None collab = None
for iface in [ for iface in [
"IBOCollaborateur3", "IBOCollaborateur3",
@ -7599,14 +7525,12 @@ class SageConnector:
if not collab: if not collab:
collab = persist collab = persist
# ===== SETDEFAULT =====
try: try:
collab.SetDefault() collab.SetDefault()
logger.info("✓ SetDefault()") logger.info("✓ SetDefault()")
except Exception as e: except Exception as e:
logger.warning(f"SetDefault() ignoré: {e}") logger.warning(f"SetDefault() ignoré: {e}")
# ===== HELPER =====
def safe_set(obj, attr, value, max_len=None): def safe_set(obj, attr, value, max_len=None):
"""Affecte une valeur de manière sécurisée""" """Affecte une valeur de manière sécurisée"""
if value is None or value == "": if value is None or value == "":
@ -7622,13 +7546,10 @@ class SageConnector:
logger.warning(f"{attr}: {e}") logger.warning(f"{attr}: {e}")
return False return False
# ===== CHAMPS DIRECTS SUR COLLABORATEUR =====
logger.info("📝 Champs directs...") logger.info("📝 Champs directs...")
# Obligatoire
safe_set(collab, "Nom", nom_upper, 35) safe_set(collab, "Nom", nom_upper, 35)
# Optionnels
safe_set(collab, "Prenom", prenom, 35) safe_set(collab, "Prenom", prenom, 35)
safe_set(collab, "Fonction", data.get("fonction"), 35) safe_set(collab, "Fonction", data.get("fonction"), 35)
safe_set(collab, "Service", data.get("service"), 35) safe_set(collab, "Service", data.get("service"), 35)
@ -7637,7 +7558,6 @@ class SageConnector:
safe_set(collab, "LinkedIn", data.get("linkedin"), 35) safe_set(collab, "LinkedIn", data.get("linkedin"), 35)
safe_set(collab, "Skype", data.get("skype"), 35) safe_set(collab, "Skype", data.get("skype"), 35)
# ===== SOUS-OBJET ADRESSE =====
logger.info("📍 Adresse...") logger.info("📍 Adresse...")
try: try:
adresse_obj = collab.Adresse adresse_obj = collab.Adresse
@ -7650,7 +7570,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Erreur Adresse: {e}") logger.warning(f"⚠️ Erreur Adresse: {e}")
# ===== SOUS-OBJET TELECOM =====
logger.info("📞 Telecom...") logger.info("📞 Telecom...")
try: try:
telecom_obj = collab.Telecom telecom_obj = collab.Telecom
@ -7661,7 +7580,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Erreur Telecom: {e}") logger.warning(f"⚠️ Erreur Telecom: {e}")
# ===== CHAMPS BOOLÉENS (seulement si True) =====
logger.info("🔘 Booléens...") logger.info("🔘 Booléens...")
if data.get("vendeur") is True: if data.get("vendeur") is True:
try: try:
@ -7690,7 +7608,6 @@ class SageConnector:
except Exception: except Exception:
pass pass
# ===== WRITE =====
logger.info("💾 Write()...") logger.info("💾 Write()...")
try: try:
collab.Write() collab.Write()
@ -7699,10 +7616,8 @@ class SageConnector:
logger.error(f" Write() échoué: {e}") logger.error(f" Write() échoué: {e}")
raise RuntimeError(f"Échec Write(): {e}") raise RuntimeError(f"Échec Write(): {e}")
# ===== RÉCUPÉRATION DU NUMÉRO =====
numero_cree = None numero_cree = None
# Via Read()
try: try:
collab.Read() collab.Read()
for attr in ["No", "CO_No", "Numero"]: for attr in ["No", "CO_No", "Numero"]:
@ -7716,7 +7631,6 @@ class SageConnector:
except Exception: except Exception:
pass pass
# Via SQL si pas trouvé
if not numero_cree: if not numero_cree:
try: try:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
@ -7737,7 +7651,6 @@ class SageConnector:
) )
logger.info(f"{'=' * 70}") logger.info(f"{'=' * 70}")
# Retourner le collaborateur
if numero_cree: if numero_cree:
return self.lire_collaborateur(numero_cree) return self.lire_collaborateur(numero_cree)
else: else:
@ -7761,13 +7674,11 @@ class SageConnector:
try: try:
with self._com_context(), self._lock_com: with self._com_context(), self._lock_com:
# ===== LECTURE DU COLLABORATEUR EXISTANT =====
try: try:
factory = self.cial.FactoryCollaborateur factory = self.cial.FactoryCollaborateur
except AttributeError: except AttributeError:
factory = self.cial.CptaApplication.FactoryCollaborateur factory = self.cial.CptaApplication.FactoryCollaborateur
# Lire par numéro
try: try:
persist = factory.ReadNumero(numero) persist = factory.ReadNumero(numero)
except Exception as e: except Exception as e:
@ -7776,7 +7687,6 @@ class SageConnector:
if not persist: if not persist:
raise ValueError(f"Collaborateur {numero} introuvable") raise ValueError(f"Collaborateur {numero} introuvable")
# Cast vers interface
collab = None collab = None
for iface in [ for iface in [
"IBOCollaborateur3", "IBOCollaborateur3",
@ -7792,14 +7702,12 @@ class SageConnector:
if not collab: if not collab:
collab = persist collab = persist
# Charger les données actuelles
try: try:
collab.Read() collab.Read()
logger.info(f"✓ Collaborateur {numero} chargé") logger.info(f"✓ Collaborateur {numero} chargé")
except Exception as e: except Exception as e:
logger.warning(f"Read() ignoré: {e}") logger.warning(f"Read() ignoré: {e}")
# ===== HELPER =====
def safe_set(obj, attr, value, max_len=None): def safe_set(obj, attr, value, max_len=None):
"""Affecte une valeur de manière sécurisée""" """Affecte une valeur de manière sécurisée"""
if value is None: if value is None:
@ -7817,7 +7725,6 @@ class SageConnector:
champs_modifies = [] champs_modifies = []
# ===== CHAMPS DIRECTS SUR COLLABORATEUR =====
logger.info("📝 Champs directs...") logger.info("📝 Champs directs...")
champs_directs = { champs_directs = {
@ -7834,13 +7741,11 @@ class SageConnector:
for py_field, (sage_attr, max_len) in champs_directs.items(): for py_field, (sage_attr, max_len) in champs_directs.items():
if py_field in data: if py_field in data:
val = data[py_field] val = data[py_field]
# Cas spécial: nom en majuscules
if py_field == "nom" and val: if py_field == "nom" and val:
val = str(val).upper().strip() val = str(val).upper().strip()
if safe_set(collab, sage_attr, val, max_len): if safe_set(collab, sage_attr, val, max_len):
champs_modifies.append(sage_attr) champs_modifies.append(sage_attr)
# ===== SOUS-OBJET ADRESSE =====
logger.info("📍 Adresse...") logger.info("📍 Adresse...")
try: try:
adresse_obj = collab.Adresse adresse_obj = collab.Adresse
@ -7864,7 +7769,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Erreur accès Adresse: {e}") logger.warning(f"⚠️ Erreur accès Adresse: {e}")
# ===== SOUS-OBJET TELECOM =====
logger.info("📞 Telecom...") logger.info("📞 Telecom...")
try: try:
telecom_obj = collab.Telecom telecom_obj = collab.Telecom
@ -7886,7 +7790,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Erreur accès Telecom: {e}") logger.warning(f"⚠️ Erreur accès Telecom: {e}")
# ===== CHAMPS BOOLÉENS =====
logger.info("🔘 Booléens...") logger.info("🔘 Booléens...")
champs_bool = { champs_bool = {
@ -7907,7 +7810,6 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"{sage_attr}: {e}") logger.warning(f"{sage_attr}: {e}")
# ===== VÉRIFICATION =====
if not champs_modifies: if not champs_modifies:
logger.info(" Aucun champ à modifier") logger.info(" Aucun champ à modifier")
return self.lire_collaborateur(numero) return self.lire_collaborateur(numero)
@ -7916,7 +7818,6 @@ class SageConnector:
f"📋 {len(champs_modifies)} champ(s) à modifier: {champs_modifies}" f"📋 {len(champs_modifies)} champ(s) à modifier: {champs_modifies}"
) )
# ===== WRITE =====
logger.info("💾 Write()...") logger.info("💾 Write()...")
try: try:
collab.Write() collab.Write()
@ -7925,7 +7826,6 @@ class SageConnector:
logger.error(f" Write() échoué: {e}") logger.error(f" Write() échoué: {e}")
raise RuntimeError(f"Échec Write(): {e}") raise RuntimeError(f"Échec Write(): {e}")
# ===== RETOUR =====
logger.info(f"\n{'=' * 70}") logger.info(f"\n{'=' * 70}")
logger.info(f" COLLABORATEUR MODIFIÉ: N°{numero}") logger.info(f" COLLABORATEUR MODIFIÉ: N°{numero}")
logger.info(f"{'=' * 70}") logger.info(f"{'=' * 70}")
@ -7952,7 +7852,6 @@ class SageConnector:
societe = society_to_dict(row) societe = society_to_dict(row)
societe["exercices"] = build_exercices(row) societe["exercices"] = build_exercices(row)
# Stocker le numéro de dossier pour la recherche du logo
self._numero_dossier = societe.get("numero_dossier") self._numero_dossier = societe.get("numero_dossier")
add_logo(societe) add_logo(societe)

View file

@ -948,7 +948,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
return val return val
# === CHAMPS DE BASE ===
article["reference"] = get_val("AR_Ref", convert_type=str) article["reference"] = get_val("AR_Ref", convert_type=str)
article["designation"] = get_val("AR_Design", convert_type=str) article["designation"] = get_val("AR_Design", convert_type=str)
article["code_ean"] = get_val("AR_CodeBarre", convert_type=str) article["code_ean"] = get_val("AR_CodeBarre", convert_type=str)
@ -956,7 +955,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["edi_code"] = get_val("AR_EdiCode", convert_type=str) article["edi_code"] = get_val("AR_EdiCode", convert_type=str)
article["raccourci"] = get_val("AR_Raccourci", convert_type=str) article["raccourci"] = get_val("AR_Raccourci", convert_type=str)
# === PRIX ===
article["prix_vente"] = get_val("AR_PrixVen", 0.0, float) article["prix_vente"] = get_val("AR_PrixVen", 0.0, float)
article["prix_achat"] = get_val("AR_PrixAch", 0.0, float) article["prix_achat"] = get_val("AR_PrixAch", 0.0, float)
article["coef"] = get_val("AR_Coef", 0.0, float) article["coef"] = get_val("AR_Coef", 0.0, float)
@ -970,44 +968,35 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["cout_standard"] = get_val("AR_CoutStd", 0.0, float) article["cout_standard"] = get_val("AR_CoutStd", 0.0, float)
# === UNITÉS ET POIDS (avec normalisation string) ===
article["unite_vente"] = normalize_string_field(get_val("AR_UniteVen")) article["unite_vente"] = normalize_string_field(get_val("AR_UniteVen"))
article["unite_poids"] = normalize_string_field(get_val("AR_UnitePoids")) article["unite_poids"] = normalize_string_field(get_val("AR_UnitePoids"))
article["poids_net"] = get_val("AR_PoidsNet", 0.0, float) article["poids_net"] = get_val("AR_PoidsNet", 0.0, float)
article["poids_brut"] = get_val("AR_PoidsBrut", 0.0, float) article["poids_brut"] = get_val("AR_PoidsBrut", 0.0, float)
# === GAMMES (avec normalisation string) ===
article["gamme_1"] = normalize_string_field(get_val("AR_Gamme1")) article["gamme_1"] = normalize_string_field(get_val("AR_Gamme1"))
article["gamme_2"] = normalize_string_field(get_val("AR_Gamme2")) article["gamme_2"] = normalize_string_field(get_val("AR_Gamme2"))
# === TYPE ARTICLE (avec libellé) ===
type_val = get_val("AR_Type", 0, int) type_val = get_val("AR_Type", 0, int)
article["type_article"] = type_val article["type_article"] = type_val
article["type_article_libelle"] = TypeArticle.get_label(type_val) article["type_article_libelle"] = TypeArticle.get_label(type_val)
# === FAMILLE ===
article["famille_code"] = get_val("FA_CodeFamille", convert_type=str) article["famille_code"] = get_val("FA_CodeFamille", convert_type=str)
# === NATURE ET GARANTIE ===
article["nature"] = get_val("AR_Nature", 0, int) article["nature"] = get_val("AR_Nature", 0, int)
article["garantie"] = get_val("AR_Garantie", 0, int) article["garantie"] = get_val("AR_Garantie", 0, int)
article["code_fiscal"] = normalize_string_field(get_val("AR_CodeFiscal")) article["code_fiscal"] = normalize_string_field(get_val("AR_CodeFiscal"))
article["pays"] = normalize_string_field(get_val("AR_Pays")) article["pays"] = normalize_string_field(get_val("AR_Pays"))
# === FOURNISSEUR ===
article["fournisseur_principal"] = get_val("CO_No", 0, int) article["fournisseur_principal"] = get_val("CO_No", 0, int)
# === CONDITIONNEMENT (avec normalisation string) ===
article["conditionnement"] = normalize_string_field(get_val("AR_Condition")) article["conditionnement"] = normalize_string_field(get_val("AR_Condition"))
article["nb_colis"] = get_val("AR_NbColis", 0, int) article["nb_colis"] = get_val("AR_NbColis", 0, int)
article["prevision"] = get_val("AR_Prevision", False, bool) article["prevision"] = get_val("AR_Prevision", False, bool)
# === SUIVI STOCK (avec libellé) ===
suivi_stock_val = normalize_enum_to_int(get_val("AR_SuiviStock")) suivi_stock_val = normalize_enum_to_int(get_val("AR_SuiviStock"))
article["suivi_stock"] = suivi_stock_val article["suivi_stock"] = suivi_stock_val
article["suivi_stock_libelle"] = SuiviStockType.get_label(suivi_stock_val) article["suivi_stock_libelle"] = SuiviStockType.get_label(suivi_stock_val)
# === NOMENCLATURE (avec libellé) ===
nomenclature_val = normalize_enum_to_int(get_val("AR_Nomencl")) nomenclature_val = normalize_enum_to_int(get_val("AR_Nomencl"))
article["nomenclature"] = nomenclature_val article["nomenclature"] = nomenclature_val
article["nomenclature_libelle"] = NomenclatureType.get_label(nomenclature_val) article["nomenclature_libelle"] = NomenclatureType.get_label(nomenclature_val)
@ -1015,7 +1004,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["qte_composant"] = get_val("AR_QteComp", 0.0, float) article["qte_composant"] = get_val("AR_QteComp", 0.0, float)
article["qte_operatoire"] = get_val("AR_QteOperatoire", 0.0, float) article["qte_operatoire"] = get_val("AR_QteOperatoire", 0.0, float)
# === STATUT ARTICLE ===
sommeil = get_val("AR_Sommeil", 0, int) sommeil = get_val("AR_Sommeil", 0, int)
article["est_actif"] = sommeil == 0 article["est_actif"] = sommeil == 0
article["en_sommeil"] = sommeil == 1 article["en_sommeil"] = sommeil == 1
@ -1023,7 +1011,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["soumis_escompte"] = get_val("AR_Escompte", False, bool) article["soumis_escompte"] = get_val("AR_Escompte", False, bool)
article["delai"] = get_val("AR_Delai", 0, int) article["delai"] = get_val("AR_Delai", 0, int)
# === STATISTIQUES ===
article["stat_01"] = get_val("AR_Stat01", convert_type=str) article["stat_01"] = get_val("AR_Stat01", convert_type=str)
article["stat_02"] = get_val("AR_Stat02", convert_type=str) article["stat_02"] = get_val("AR_Stat02", convert_type=str)
article["stat_03"] = get_val("AR_Stat03", convert_type=str) article["stat_03"] = get_val("AR_Stat03", convert_type=str)
@ -1031,17 +1018,14 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["stat_05"] = get_val("AR_Stat05", convert_type=str) article["stat_05"] = get_val("AR_Stat05", convert_type=str)
article["hors_statistique"] = get_val("AR_HorsStat", False, bool) article["hors_statistique"] = get_val("AR_HorsStat", False, bool)
# === CATÉGORIES COMPTABLES ===
article["categorie_1"] = get_val("CL_No1", 0, int) article["categorie_1"] = get_val("CL_No1", 0, int)
article["categorie_2"] = get_val("CL_No2", 0, int) article["categorie_2"] = get_val("CL_No2", 0, int)
article["categorie_3"] = get_val("CL_No3", 0, int) article["categorie_3"] = get_val("CL_No3", 0, int)
article["categorie_4"] = get_val("CL_No4", 0, int) article["categorie_4"] = get_val("CL_No4", 0, int)
# === DATE MODIFICATION ===
date_modif = get_val("AR_DateModif") date_modif = get_val("AR_DateModif")
article["date_modification"] = str(date_modif) if date_modif else None article["date_modification"] = str(date_modif) if date_modif else None
# === PARAMÈTRES DE VENTE ===
article["vente_debit"] = get_val("AR_VteDebit", False, bool) article["vente_debit"] = get_val("AR_VteDebit", False, bool)
article["non_imprimable"] = get_val("AR_NotImp", False, bool) article["non_imprimable"] = get_val("AR_NotImp", False, bool)
article["transfere"] = get_val("AR_Transfere", False, bool) article["transfere"] = get_val("AR_Transfere", False, bool)
@ -1054,7 +1038,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["sous_traitance"] = get_val("AR_SousTraitance", False, bool) article["sous_traitance"] = get_val("AR_SousTraitance", False, bool)
article["criticite"] = get_val("AR_Criticite", 0, int) article["criticite"] = get_val("AR_Criticite", 0, int)
# === PARAMÈTRES DE PRODUCTION ===
article["reprise_code_defaut"] = normalize_string_field(get_val("RP_CodeDefaut")) article["reprise_code_defaut"] = normalize_string_field(get_val("RP_CodeDefaut"))
article["delai_fabrication"] = get_val("AR_DelaiFabrication", 0, int) article["delai_fabrication"] = get_val("AR_DelaiFabrication", 0, int)
article["delai_peremption"] = get_val("AR_DelaiPeremption", 0, int) article["delai_peremption"] = get_val("AR_DelaiPeremption", 0, int)
@ -1062,12 +1045,10 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["type_lancement"] = get_val("AR_TypeLancement", 0, int) article["type_lancement"] = get_val("AR_TypeLancement", 0, int)
article["cycle"] = get_val("AR_Cycle", 1, int) article["cycle"] = get_val("AR_Cycle", 1, int)
# === MÉDIA ET LANGUES ===
article["photo"] = get_val("AR_Photo", convert_type=str) article["photo"] = get_val("AR_Photo", convert_type=str)
article["langue_1"] = get_val("AR_Langue1", convert_type=str) article["langue_1"] = get_val("AR_Langue1", convert_type=str)
article["langue_2"] = get_val("AR_Langue2", convert_type=str) article["langue_2"] = get_val("AR_Langue2", convert_type=str)
# === FRAIS ===
article["frais_01_denomination"] = get_val( article["frais_01_denomination"] = get_val(
"AR_Frais01FR_Denomination", convert_type=str "AR_Frais01FR_Denomination", convert_type=str
) )
@ -1078,7 +1059,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
"AR_Frais03FR_Denomination", convert_type=str "AR_Frais03FR_Denomination", convert_type=str
) )
# === CHAMPS PERSONNALISÉS ===
article["marque_commerciale"] = get_val("Marque commerciale", convert_type=str) article["marque_commerciale"] = get_val("Marque commerciale", convert_type=str)
objectif_val = get_val("Objectif / Qtés vendues") objectif_val = get_val("Objectif / Qtés vendues")
@ -1103,7 +1083,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["interdire_commande"] = get_val("AR_InterdireCommande", False, bool) article["interdire_commande"] = get_val("AR_InterdireCommande", False, bool)
article["exclure"] = get_val("AR_Exclure", False, bool) article["exclure"] = get_val("AR_Exclure", False, bool)
# === INITIALISATION DES CHAMPS DE STOCK (remplis par enrichissement) ===
article["stock_reel"] = 0.0 article["stock_reel"] = 0.0
article["stock_mini"] = 0.0 article["stock_mini"] = 0.0
article["stock_maxi"] = 0.0 article["stock_maxi"] = 0.0
@ -1111,7 +1090,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["stock_commande"] = 0.0 article["stock_commande"] = 0.0
article["stock_disponible"] = 0.0 article["stock_disponible"] = 0.0
# === INITIALISATION DES CHAMPS DE FAMILLE (remplis par enrichissement) ===
article["famille_libelle"] = None article["famille_libelle"] = None
article["famille_type"] = None article["famille_type"] = None
article["famille_unite_vente"] = None article["famille_unite_vente"] = None
@ -1128,7 +1106,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
article["famille_hors_stat"] = None article["famille_hors_stat"] = None
article["famille_pays"] = None article["famille_pays"] = None
# === INITIALISATION DES CHAMPS FOURNISSEUR/TVA (remplis par enrichissement) ===
article["fournisseur_nom"] = None article["fournisseur_nom"] = None
article["tva_code"] = None article["tva_code"] = None
article["tva_taux"] = None article["tva_taux"] = None
@ -1257,7 +1234,6 @@ def enrichir_fournisseurs_articles(articles: List[Dict], cursor) -> List[Dict]:
nb_enrichis = 0 nb_enrichis = 0
for article in articles: for article in articles:
num_fourn = article.get("fournisseur_principal") num_fourn = article.get("fournisseur_principal")
# Convertir en string pour correspondre au fournisseur_map
num_fourn_str = ( num_fourn_str = (
str(num_fourn).strip() if num_fourn not in (None, "", " ") else None str(num_fourn).strip() if num_fourn not in (None, "", " ") else None
) )

View file

@ -13,7 +13,6 @@ def _get_journal_auto(self, mode_reglement: int) -> str:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Mode Espèces = 2 (selon l'image Sage fournie)
if mode_reglement == 2: # Espèces if mode_reglement == 2: # Espèces
cursor.execute(""" cursor.execute("""
SELECT TOP 1 JO_Num SELECT TOP 1 JO_Num
@ -25,7 +24,6 @@ def _get_journal_auto(self, mode_reglement: int) -> str:
ORDER BY JO_Num ORDER BY JO_Num
""") """)
else: else:
# Autres modes → Banque
cursor.execute(""" cursor.execute("""
SELECT TOP 1 JO_Num SELECT TOP 1 JO_Num
FROM F_JOURNAUX FROM F_JOURNAUX
@ -40,7 +38,6 @@ def _get_journal_auto(self, mode_reglement: int) -> str:
if row: if row:
return row[0].strip() return row[0].strip()
# Fallback: premier journal de trésorerie disponible
cursor.execute(""" cursor.execute("""
SELECT TOP 1 JO_Num SELECT TOP 1 JO_Num
FROM F_JOURNAUX FROM F_JOURNAUX
@ -74,14 +71,12 @@ def _valider_coherence_journal_mode(self, code_journal: str, mode_reglement: int
compte_general = (row[0] or "").strip() compte_general = (row[0] or "").strip()
# Mode Espèces (2) doit utiliser un journal caisse (53x)
if mode_reglement == 2: if mode_reglement == 2:
if not compte_general.startswith("53"): if not compte_general.startswith("53"):
logger.warning( logger.warning(
f"Mode Espèces avec journal non-caisse ({code_journal}, compte {compte_general})" f"Mode Espèces avec journal non-caisse ({code_journal}, compte {compte_general})"
) )
else: else:
# Autres modes doivent utiliser un journal banque (51x)
if compte_general.startswith("53"): if compte_general.startswith("53"):
logger.warning( logger.warning(
f"Mode non-espèces avec journal caisse ({code_journal}, compte {compte_general})" f"Mode non-espèces avec journal caisse ({code_journal}, compte {compte_general})"
@ -105,7 +100,6 @@ def _get_mode_reglement_libelle(self, mode_reglement: int) -> str:
if row: if row:
return (row[0] or "").strip() return (row[0] or "").strip()
# Fallback sur les libellés standards
libelles = { libelles = {
0: "Chèque", 0: "Chèque",
1: "Virement", 1: "Virement",
@ -319,7 +313,6 @@ def lire_tous_reglements(
facture = _format_facture(row, echeances) facture = _format_facture(row, echeances)
# Filtrer par statut si demandé
if statut_reglement: if statut_reglement:
reste = facture["montants"]["reste_a_regler"] reste = facture["montants"]["reste_a_regler"]
montant_regle = facture["montants"]["montant_regle"] montant_regle = facture["montants"]["montant_regle"]
@ -509,8 +502,6 @@ def lire_facture_reglement_detail(self, do_piece: str) -> Dict:
cursor, do_domaine, do_type, do_piece, montant_ttc cursor, do_domaine, do_type, do_piece, montant_ttc
) )
# Utiliser la même structure que lire_tous_reglements
# mais avec les infos complètes du client
types_doc = {6: "Facture", 7: "Avoir"} types_doc = {6: "Facture", 7: "Avoir"}
montant_regle = sum(e["montant_regle"] for e in echeances) montant_regle = sum(e["montant_regle"] for e in echeances)
reste_a_regler = montant_ttc - montant_regle reste_a_regler = montant_ttc - montant_regle
@ -607,7 +598,6 @@ def lire_reglement_detail(self, rg_no: int) -> Dict:
if not row: if not row:
raise ValueError(f"Règlement {rg_no} introuvable") raise ValueError(f"Règlement {rg_no} introuvable")
# Récupérer les imputations
cursor.execute( cursor.execute(
""" """
SELECT SELECT
@ -766,11 +756,9 @@ def regler_facture(
date_reglement = date_reglement or datetime.now() date_reglement = date_reglement or datetime.now()
# Déduction automatique du journal si non fourni
if not code_journal: if not code_journal:
code_journal = _get_journal_auto(self, mode_reglement) code_journal = _get_journal_auto(self, mode_reglement)
else: else:
# Valider la cohérence journal/mode
_valider_coherence_journal_mode(self, code_journal, mode_reglement) _valider_coherence_journal_mode(self, code_journal, mode_reglement)
logger.info( logger.info(
@ -801,7 +789,6 @@ def regler_facture(
f"Montant ({montant}€) supérieur au solde ({solde_actuel:.2f}€)" f"Montant ({montant}€) supérieur au solde ({solde_actuel:.2f}€)"
) )
# Récupérer le client
client_code = "" client_code = ""
try: try:
client_obj = getattr(doc, "Client", None) client_obj = getattr(doc, "Client", None)
@ -811,12 +798,10 @@ def regler_facture(
except Exception: except Exception:
pass pass
# Récupérer l'échéance
echeance = _get_premiere_echeance(doc) echeance = _get_premiere_echeance(doc)
if not echeance: if not echeance:
raise ValueError(f"Facture {numero_facture} sans échéance") raise ValueError(f"Facture {numero_facture} sans échéance")
# Exécuter le règlement
numero_reglement = _executer_reglement_com( numero_reglement = _executer_reglement_com(
self, self,
doc=doc, doc=doc,
@ -847,7 +832,6 @@ def regler_facture(
nouveau_solde = total_ttc - nouveau_montant_regle nouveau_solde = total_ttc - nouveau_montant_regle
logger.info(f"Règlement effectué - Solde restant: {nouveau_solde:.2f}") logger.info(f"Règlement effectué - Solde restant: {nouveau_solde:.2f}")
# Récupérer le libellé du mode règlement
mode_libelle = _get_mode_reglement_libelle(self, mode_reglement) mode_libelle = _get_mode_reglement_libelle(self, mode_reglement)
return { return {
@ -916,17 +900,14 @@ def _executer_reglement_com(
): ):
erreurs = [] erreurs = []
# APPROCHE PRINCIPALE: Créer règlement complet, l'écrire, puis l'assigner au process
try: try:
logger.info("Création du règlement via FactoryDocumentReglement...") logger.info("Création du règlement via FactoryDocumentReglement...")
# 1. Créer le règlement
factory_reg = self.cial.FactoryDocumentReglement factory_reg = self.cial.FactoryDocumentReglement
reg = factory_reg.Create() reg = factory_reg.Create()
reg = win32com.client.CastTo(reg, "IBODocumentReglement") reg = win32com.client.CastTo(reg, "IBODocumentReglement")
logger.info(" Règlement créé et casté vers IBODocumentReglement") logger.info(" Règlement créé et casté vers IBODocumentReglement")
# 2. Configurer le Journal (objet)
try: try:
journal_factory = self.cial.CptaApplication.FactoryJournal journal_factory = self.cial.CptaApplication.FactoryJournal
journal_persist = journal_factory.ReadNumero(code_journal) journal_persist = journal_factory.ReadNumero(code_journal)
@ -936,7 +917,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.warning(f" Journal: {e}") logger.warning(f" Journal: {e}")
# 3. Configurer le TiersPayeur (objet client)
try: try:
factory_client = self.cial.CptaApplication.FactoryClient factory_client = self.cial.CptaApplication.FactoryClient
if client_code: if client_code:
@ -947,7 +927,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.warning(f" TiersPayeur: {e}") logger.warning(f" TiersPayeur: {e}")
# 4. Configurer les champs simples
try: try:
reg.RG_Date = pywintypes.Time(date_reglement) reg.RG_Date = pywintypes.Time(date_reglement)
logger.info(f" RG_Date: {date_reglement}") logger.info(f" RG_Date: {date_reglement}")
@ -960,7 +939,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.warning(f" RG_Montant: {e}") logger.warning(f" RG_Montant: {e}")
# 5. Mode de règlement via l'objet Reglement
try: try:
mode_factory = getattr( mode_factory = getattr(
self.cial.CptaApplication, "FactoryModeReglement", None self.cial.CptaApplication, "FactoryModeReglement", None
@ -973,7 +951,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.debug(f" Mode règlement via factory: {e}") logger.debug(f" Mode règlement via factory: {e}")
# 6. Devise
if devise_code != 0: if devise_code != 0:
try: try:
reg.RG_Devise = devise_code reg.RG_Devise = devise_code
@ -987,7 +964,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.debug(f" RG_Cours: {e}") logger.debug(f" RG_Cours: {e}")
# Montant en devise
try: try:
montant_devise = montant * cours_devise montant_devise = montant * cours_devise
reg.RG_MontantDev = montant_devise reg.RG_MontantDev = montant_devise
@ -995,7 +971,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.debug(f" RG_MontantDev: {e}") logger.debug(f" RG_MontantDev: {e}")
# 7. TVA sur encaissement
if tva_encaissement: if tva_encaissement:
try: try:
reg.RG_Encaissement = 1 reg.RG_Encaissement = 1
@ -1003,7 +978,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.debug(f" RG_Encaissement: {e}") logger.debug(f" RG_Encaissement: {e}")
# 8. Compte général spécifique
if compte_general: if compte_general:
try: try:
cg_factory = self.cial.CptaApplication.FactoryCompteG cg_factory = self.cial.CptaApplication.FactoryCompteG
@ -1014,7 +988,6 @@ def _executer_reglement_com(
except Exception as e: except Exception as e:
logger.debug(f" CompteG: {e}") logger.debug(f" CompteG: {e}")
# 9. Référence et libellé
if reference: if reference:
try: try:
reg.RG_Reference = reference reg.RG_Reference = reference
@ -1039,12 +1012,10 @@ def _executer_reglement_com(
except Exception: except Exception:
pass pass
# 10. ÉCRIRE le règlement
reg.Write() reg.Write()
numero = getattr(reg, "RG_Piece", None) numero = getattr(reg, "RG_Piece", None)
logger.info(f" Règlement écrit avec numéro: {numero}") logger.info(f" Règlement écrit avec numéro: {numero}")
# 11. Créer le lien règlement-échéance via la factory DU RÈGLEMENT
try: try:
logger.info(" Création du lien règlement-échéance...") logger.info(" Création du lien règlement-échéance...")
factory_reg_ech = getattr(reg, "FactoryDocumentReglementEcheance", None) factory_reg_ech = getattr(reg, "FactoryDocumentReglementEcheance", None)
@ -1052,7 +1023,6 @@ def _executer_reglement_com(
if factory_reg_ech: if factory_reg_ech:
reg_ech = factory_reg_ech.Create() reg_ech = factory_reg_ech.Create()
# Cast vers IBODocumentReglementEcheance
for iface in [ for iface in [
"IBODocumentReglementEcheance3", "IBODocumentReglementEcheance3",
"IBODocumentReglementEcheance", "IBODocumentReglementEcheance",
@ -1064,21 +1034,18 @@ def _executer_reglement_com(
except Exception: except Exception:
continue continue
# Définir l'échéance
try: try:
reg_ech.Echeance = echeance reg_ech.Echeance = echeance
logger.info(" Echeance définie") logger.info(" Echeance définie")
except Exception as e: except Exception as e:
logger.warning(f" Echeance: {e}") logger.warning(f" Echeance: {e}")
# Définir le montant
try: try:
reg_ech.RC_Montant = montant reg_ech.RC_Montant = montant
logger.info(f" RC_Montant: {montant}") logger.info(f" RC_Montant: {montant}")
except Exception as e: except Exception as e:
logger.warning(f" RC_Montant: {e}") logger.warning(f" RC_Montant: {e}")
# Écrire le lien
try: try:
reg_ech.SetDefault() reg_ech.SetDefault()
except Exception: except Exception:
@ -1093,16 +1060,13 @@ def _executer_reglement_com(
erreurs.append(f"Lien échéance: {e}") erreurs.append(f"Lien échéance: {e}")
logger.warning(f" Erreur création lien: {e}") logger.warning(f" Erreur création lien: {e}")
# Si le lien a échoué, essayer via le process
logger.info(" Tentative via CreateProcess_ReglerEcheances...") logger.info(" Tentative via CreateProcess_ReglerEcheances...")
try: try:
process = self.cial.CreateProcess_ReglerEcheances() process = self.cial.CreateProcess_ReglerEcheances()
# Assigner le règlement déjà écrit
process.Reglement = reg process.Reglement = reg
logger.info(" Règlement assigné au process") logger.info(" Règlement assigné au process")
# Ajouter l'échéance
try: try:
process.AddDocumentEcheanceMontant(echeance, montant) process.AddDocumentEcheanceMontant(echeance, montant)
logger.info(" Échéance ajoutée avec montant") logger.info(" Échéance ajoutée avec montant")
@ -1134,7 +1098,6 @@ def _executer_reglement_com(
erreurs.append(f"FactoryDocumentReglement: {e}") erreurs.append(f"FactoryDocumentReglement: {e}")
logger.error(f"FactoryDocumentReglement échoué: {e}") logger.error(f"FactoryDocumentReglement échoué: {e}")
# APPROCHE ALTERNATIVE: Via le mode règlement de l'échéance
try: try:
logger.info("Tentative via modification directe de l'échéance...") logger.info("Tentative via modification directe de l'échéance...")
@ -1165,7 +1128,6 @@ def _executer_reglement_com(
new_reg = factory_reg.Create() new_reg = factory_reg.Create()
new_reg = win32com.client.CastTo(new_reg, "IBODocumentReglement") new_reg = win32com.client.CastTo(new_reg, "IBODocumentReglement")
# Configurer
journal_factory = self.cial.CptaApplication.FactoryJournal journal_factory = self.cial.CptaApplication.FactoryJournal
journal_persist = journal_factory.ReadNumero(code_journal) journal_persist = journal_factory.ReadNumero(code_journal)
if journal_persist: if journal_persist:
@ -1181,7 +1143,6 @@ def _executer_reglement_com(
new_reg.RG_Montant = montant new_reg.RG_Montant = montant
new_reg.RG_Impute = 1 new_reg.RG_Impute = 1
# Devise si non EUR
if devise_code != 0: if devise_code != 0:
try: try:
new_reg.RG_Devise = devise_code new_reg.RG_Devise = devise_code
@ -1190,14 +1151,12 @@ def _executer_reglement_com(
except Exception: except Exception:
pass pass
# TVA encaissement
if tva_encaissement: if tva_encaissement:
try: try:
new_reg.RG_Encaissement = 1 new_reg.RG_Encaissement = 1
except Exception: except Exception:
pass pass
# Compte général
if compte_general: if compte_general:
try: try:
cg_factory = self.cial.CptaApplication.FactoryCompteG cg_factory = self.cial.CptaApplication.FactoryCompteG
@ -1239,7 +1198,6 @@ def introspecter_reglement(self):
result = {} result = {}
try: try:
with self._com_context(), self._lock_com: with self._com_context(), self._lock_com:
# IBODocumentReglement et sa factory de liens
try: try:
factory = self.cial.FactoryDocumentReglement factory = self.cial.FactoryDocumentReglement
reg = factory.Create() reg = factory.Create()
@ -1248,7 +1206,6 @@ def introspecter_reglement(self):
a for a in dir(reg) if not a.startswith("_") a for a in dir(reg) if not a.startswith("_")
] ]
# FactoryDocumentReglementEcheance depuis le règlement
factory_lien = getattr(reg, "FactoryDocumentReglementEcheance", None) factory_lien = getattr(reg, "FactoryDocumentReglementEcheance", None)
if factory_lien: if factory_lien:
lien = factory_lien.Create() lien = factory_lien.Create()
@ -1271,14 +1228,12 @@ def introspecter_reglement(self):
except Exception as e: except Exception as e:
result["error_reglement"] = str(e) result["error_reglement"] = str(e)
# Process
try: try:
process = self.cial.CreateProcess_ReglerEcheances() process = self.cial.CreateProcess_ReglerEcheances()
result["Process"] = [a for a in dir(process) if not a.startswith("_")] result["Process"] = [a for a in dir(process) if not a.startswith("_")]
except Exception as e: except Exception as e:
result["error_process"] = str(e) result["error_process"] = str(e)
# Échéance et ses attributs
try: try:
factory_doc = self.cial.FactoryDocumentVente factory_doc = self.cial.FactoryDocumentVente
doc_list = factory_doc.List doc_list = factory_doc.List
@ -1306,7 +1261,6 @@ def introspecter_reglement(self):
if not a.startswith("_") if not a.startswith("_")
] ]
# FactoryDocumentReglementEcheance depuis l'échéance
factory_lien_ech = getattr( factory_lien_ech = getattr(
ech, ech,
"FactoryDocumentReglementEcheance", "FactoryDocumentReglementEcheance",
@ -1342,7 +1296,6 @@ def introspecter_reglement(self):
except Exception: except Exception:
pass pass
# Reglement de l'échéance (mode)
mode = getattr(ech, "Reglement", None) mode = getattr(ech, "Reglement", None)
if mode: if mode:
result["Echeance_Reglement_mode"] = [ result["Echeance_Reglement_mode"] = [
@ -1382,7 +1335,6 @@ def regler_factures_client(
date_reglement = date_reglement or datetime.now() date_reglement = date_reglement or datetime.now()
# Déduction automatique du journal si non fourni
if not code_journal: if not code_journal:
code_journal = _get_journal_auto(self, mode_reglement) code_journal = _get_journal_auto(self, mode_reglement)
@ -1610,7 +1562,6 @@ def lire_devises(self) -> List[Dict]:
with self._get_sql_connection() as conn: with self._get_sql_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Vérifier d'abord si F_DEVISE existe
cursor.execute(""" cursor.execute("""
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'F_DEVISE' WHERE TABLE_NAME = 'F_DEVISE'
@ -1640,17 +1591,14 @@ def lire_devises(self) -> List[Dict]:
if devises: if devises:
return devises return devises
# Fallback: Lire depuis P_DOSSIER
cursor.execute(""" cursor.execute("""
SELECT N_DeviseCompte, N_DeviseEquival SELECT N_DeviseCompte, N_DeviseEquival
FROM P_DOSSIER FROM P_DOSSIER
""") """)
row = cursor.fetchone() row = cursor.fetchone()
# Devise par défaut basée sur la config dossier
devise_principale = row[0] if row else 0 devise_principale = row[0] if row else 0
# Retourner les devises standards
devises_standards = [ devises_standards = [
{ {
"code": 0, "code": 0,
@ -1709,7 +1657,6 @@ def lire_journaux_tresorerie(self) -> List[Dict]:
journaux = [] journaux = []
for row in cursor.fetchall(): for row in cursor.fetchall():
compte_general = (row[2] or "").strip() compte_general = (row[2] or "").strip()
# Déterminer le type basé sur le compte général
if compte_general.startswith("53"): if compte_general.startswith("53"):
type_journal = "caisse" type_journal = "caisse"
elif compte_general.startswith("51"): elif compte_general.startswith("51"):
@ -1735,7 +1682,6 @@ def lire_comptes_generaux(
if not self.cial: if not self.cial:
raise RuntimeError("Connexion Sage non établie") raise RuntimeError("Connexion Sage non établie")
# Mapping type -> préfixes de comptes
prefixes_map = { prefixes_map = {
"client": ["411"], "client": ["411"],
"fournisseur": ["401"], "fournisseur": ["401"],
@ -1763,7 +1709,6 @@ def lire_comptes_generaux(
""" """
params = [] params = []
# Appliquer les filtres
if type_compte and type_compte in prefixes_map: if type_compte and type_compte in prefixes_map:
prefixes = prefixes_map[type_compte] prefixes = prefixes_map[type_compte]
conditions = " OR ".join(["CG_Num LIKE ?" for _ in prefixes]) conditions = " OR ".join(["CG_Num LIKE ?" for _ in prefixes])

View file

@ -104,7 +104,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
persist = factory.ReadPiece(60, numero_facture) persist = factory.ReadPiece(60, numero_facture)
# 1. Attributs du persist brut
persist_attrs = [a for a in dir(persist) if not a.startswith("_")] persist_attrs = [a for a in dir(persist) if not a.startswith("_")]
result["persist"]["all_attrs"] = persist_attrs result["persist"]["all_attrs"] = persist_attrs
result["persist"]["methods"] = [] result["persist"]["methods"] = []
@ -127,7 +126,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
{"name": attr, "error": str(e)[:50]} {"name": attr, "error": str(e)[:50]}
) )
# Chercher spécifiquement les attributs liés à validation/valide
result["persist"]["validation_related"] = [ result["persist"]["validation_related"] = [
a a
for a in persist_attrs for a in persist_attrs
@ -137,7 +135,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
) )
] ]
# 2. IBODocumentVente3
try: try:
doc = win32com.client.CastTo(persist, "IBODocumentVente3") doc = win32com.client.CastTo(persist, "IBODocumentVente3")
doc.Read() doc.Read()
@ -147,7 +144,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
result["IBODocumentVente3"]["methods"] = [] result["IBODocumentVente3"]["methods"] = []
result["IBODocumentVente3"]["properties_with_values"] = [] result["IBODocumentVente3"]["properties_with_values"] = []
# Lister les méthodes
for attr in doc_attrs: for attr in doc_attrs:
try: try:
val = getattr(doc, attr, None) val = getattr(doc, attr, None)
@ -156,7 +152,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
except Exception: except Exception:
pass pass
# Chercher DO_* properties
result["IBODocumentVente3"]["DO_properties"] = [] result["IBODocumentVente3"]["DO_properties"] = []
for attr in doc_attrs: for attr in doc_attrs:
if attr.startswith("DO_"): if attr.startswith("DO_"):
@ -170,7 +165,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
{"name": attr, "error": str(e)[:50]} {"name": attr, "error": str(e)[:50]}
) )
# Chercher les attributs liés à validation
result["IBODocumentVente3"]["validation_related"] = [ result["IBODocumentVente3"]["validation_related"] = [
a a
for a in doc_attrs for a in doc_attrs
@ -183,7 +177,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
except Exception as e: except Exception as e:
result["IBODocumentVente3"]["error"] = str(e) result["IBODocumentVente3"]["error"] = str(e)
# 3. IBODocument3
try: try:
doc3 = win32com.client.CastTo(persist, "IBODocument3") doc3 = win32com.client.CastTo(persist, "IBODocument3")
doc3.Read() doc3.Read()
@ -202,7 +195,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
except Exception as e: except Exception as e:
result["IBODocument3"]["error"] = str(e) result["IBODocument3"]["error"] = str(e)
# 4. IPMDocument
try: try:
pmdoc = win32com.client.CastTo(persist, "IPMDocument") pmdoc = win32com.client.CastTo(persist, "IPMDocument")
@ -215,7 +207,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
except Exception as e: except Exception as e:
result["IPMDocument"]["error"] = str(e) result["IPMDocument"]["error"] = str(e)
# 5. Chercher FactoryDocument* sur le document
result["factories_on_doc"] = [] result["factories_on_doc"] = []
for attr in persist_attrs: for attr in persist_attrs:
if "Factory" in attr: if "Factory" in attr:
@ -235,13 +226,11 @@ def introspecter_validation(connector, numero_facture: str = None) -> Dict:
try: try:
with connector._com_context(), connector._lock_com: with connector._com_context(), connector._lock_com:
# Tous les CreateProcess
cial_attrs = [a for a in dir(connector.cial) if not a.startswith("_")] cial_attrs = [a for a in dir(connector.cial) if not a.startswith("_")]
result["all_createprocess"] = [ result["all_createprocess"] = [
a for a in cial_attrs if "CreateProcess" in a a for a in cial_attrs if "CreateProcess" in a
] ]
# Explorer chaque process
for process_name in result["all_createprocess"]: for process_name in result["all_createprocess"]:
try: try:
process = getattr(connector.cial, process_name)() process = getattr(connector.cial, process_name)()
@ -255,7 +244,6 @@ def introspecter_validation(connector, numero_facture: str = None) -> Dict:
except Exception as e: except Exception as e:
result[process_name] = {"error": str(e)} result[process_name] = {"error": str(e)}
# Introspection document si fourni
if numero_facture: if numero_facture:
result["document"] = introspecter_document_complet( result["document"] = introspecter_document_complet(
connector, numero_facture connector, numero_facture
@ -270,11 +258,9 @@ def introspecter_validation(connector, numero_facture: str = None) -> Dict:
def valider_facture(connector, numero_facture: str) -> Dict: def valider_facture(connector, numero_facture: str) -> Dict:
logger.info(f" Validation facture {numero_facture} (SQL direct)") logger.info(f" Validation facture {numero_facture} (SQL direct)")
# Vérifications préalables
with connector._get_sql_connection() as conn: with connector._get_sql_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Vérifier que la facture existe et peut être validée
cursor.execute( cursor.execute(
""" """
SELECT DO_Valide, DO_Statut, DO_TotalTTC, DO_MontantRegle SELECT DO_Valide, DO_Statut, DO_TotalTTC, DO_MontantRegle
@ -296,7 +282,6 @@ def valider_facture(connector, numero_facture: str) -> Dict:
if statut == 6: # Annulé if statut == 6: # Annulé
raise ValueError("Facture annulée, validation impossible") raise ValueError("Facture annulée, validation impossible")
# Valider via SQL
cursor.execute( cursor.execute(
""" """
UPDATE F_DOCENTETE UPDATE F_DOCENTETE
@ -308,7 +293,6 @@ def valider_facture(connector, numero_facture: str) -> Dict:
conn.commit() conn.commit()
# Vérifier
cursor.execute( cursor.execute(
""" """
SELECT DO_Valide, DO_Imprim SELECT DO_Valide, DO_Imprim
@ -400,7 +384,6 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
if not persist: if not persist:
raise ValueError(f"Impossible de lire la facture {numero_facture}") raise ValueError(f"Impossible de lire la facture {numero_facture}")
# APPROCHE 1: Accès direct à DO_Valide sur IBODocumentVente3
try: try:
logger.info( logger.info(
" APPROCHE 1: Modification directe DO_Valide sur IBODocumentVente3..." " APPROCHE 1: Modification directe DO_Valide sur IBODocumentVente3..."
@ -408,15 +391,12 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
doc = win32com.client.CastTo(persist, "IBODocumentVente3") doc = win32com.client.CastTo(persist, "IBODocumentVente3")
doc.Read() doc.Read()
# Vérifier la valeur actuelle
valeur_avant = getattr(doc, "DO_Valide", None) valeur_avant = getattr(doc, "DO_Valide", None)
logger.info(f" DO_Valide avant: {valeur_avant}") logger.info(f" DO_Valide avant: {valeur_avant}")
# Tenter la modification
doc.DO_Valide = valeur_cible doc.DO_Valide = valeur_cible
doc.Write() doc.Write()
# Relire pour vérifier
doc.Read() doc.Read()
valeur_apres = getattr(doc, "DO_Valide", None) valeur_apres = getattr(doc, "DO_Valide", None)
logger.info(f" DO_Valide après: {valeur_apres}") logger.info(f" DO_Valide après: {valeur_apres}")
@ -433,7 +413,6 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
erreurs.append(f"IBODocumentVente3.DO_Valide: {e}") erreurs.append(f"IBODocumentVente3.DO_Valide: {e}")
logger.warning(f" Erreur: {e}") logger.warning(f" Erreur: {e}")
# APPROCHE 2: Via IBODocument3 (interface parent)
try: try:
logger.info(" APPROCHE 2: Via IBODocument3...") logger.info(" APPROCHE 2: Via IBODocument3...")
doc3 = win32com.client.CastTo(persist, "IBODocument3") doc3 = win32com.client.CastTo(persist, "IBODocument3")
@ -450,7 +429,6 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
except Exception as e: except Exception as e:
erreurs.append(f"IBODocument3: {e}") erreurs.append(f"IBODocument3: {e}")
# APPROCHE 3: Chercher un CreateProcess de validation
try: try:
logger.info(" APPROCHE 3: Recherche CreateProcess de validation...") logger.info(" APPROCHE 3: Recherche CreateProcess de validation...")
cial_attrs = [a for a in dir(connector.cial) if "CreateProcess" in a] cial_attrs = [a for a in dir(connector.cial) if "CreateProcess" in a]
@ -465,7 +443,6 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
for proc_name in validation_processes: for proc_name in validation_processes:
try: try:
process = getattr(connector.cial, proc_name)() process = getattr(connector.cial, proc_name)()
# Lister les attributs du process
proc_attrs = [a for a in dir(process) if not a.startswith("_")] proc_attrs = [a for a in dir(process) if not a.startswith("_")]
logger.info(f" {proc_name} attrs: {proc_attrs}") logger.info(f" {proc_name} attrs: {proc_attrs}")
@ -483,7 +460,6 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
except Exception as e: except Exception as e:
erreurs.append(f"CreateProcess: {e}") erreurs.append(f"CreateProcess: {e}")
# APPROCHE 4: WriteDefault avec paramètres
try: try:
logger.info(" APPROCHE 4: WriteDefault...") logger.info(" APPROCHE 4: WriteDefault...")
doc = win32com.client.CastTo(persist, "IBODocumentVente3") doc = win32com.client.CastTo(persist, "IBODocumentVente3")
@ -510,7 +486,6 @@ def explorer_toutes_interfaces_validation(connector, numero_facture: str) -> Dic
factory = connector.cial.FactoryDocumentVente factory = connector.cial.FactoryDocumentVente
persist = factory.ReadPiece(60, numero_facture) persist = factory.ReadPiece(60, numero_facture)
# Liste des interfaces à tester
interfaces = [ interfaces = [
"IBODocumentVente3", "IBODocumentVente3",
"IBODocument3", "IBODocument3",
@ -544,7 +519,6 @@ def explorer_toutes_interfaces_validation(connector, numero_facture: str) -> Dic
props[names[0]] = { props[names[0]] = {
"memid": func_desc.memid, "memid": func_desc.memid,
"invkind": func_desc.invkind, "invkind": func_desc.invkind,
# invkind: 1=METHOD, 2=GET, 4=PUT, 8=PUTREF
"has_setter": (func_desc.invkind & 4) == 4, "has_setter": (func_desc.invkind & 4) == 4,
} }
@ -556,7 +530,6 @@ def explorer_toutes_interfaces_validation(connector, numero_facture: str) -> Dic
except Exception as e: except Exception as e:
result["interfaces"][iface_name] = {"error": str(e)[:100]} result["interfaces"][iface_name] = {"error": str(e)[:100]}
# Explorer aussi FactoryDocumentVente pour des méthodes de validation
try: try:
factory_attrs = [a for a in dir(factory) if not a.startswith("_")] factory_attrs = [a for a in dir(factory) if not a.startswith("_")]
result["factory_methods"] = [ result["factory_methods"] = [

View file

@ -27,7 +27,6 @@ def creer_document_vente(
transaction_active = False transaction_active = False
try: try:
# Démarrage transaction
try: try:
self.cial.CptaApplication.BeginTrans() self.cial.CptaApplication.BeginTrans()
transaction_active = True transaction_active = True
@ -35,7 +34,6 @@ def creer_document_vente(
except Exception as e: except Exception as e:
logger.warning(f"BeginTrans échoué (non critique): {e}") logger.warning(f"BeginTrans échoué (non critique): {e}")
# Création du document
process = self.cial.CreateProcess_Document(config.type_sage) process = self.cial.CreateProcess_Document(config.type_sage)
doc = process.Document doc = process.Document
@ -46,13 +44,11 @@ def creer_document_vente(
logger.info(f"✓ Document {config.nom_document} créé") logger.info(f"✓ Document {config.nom_document} créé")
# ===== DATES =====
date_principale = normaliser_date( date_principale = normaliser_date(
doc_data.get(config.champ_date_principale) doc_data.get(config.champ_date_principale)
) )
doc.DO_Date = pywintypes.Time(date_principale) doc.DO_Date = pywintypes.Time(date_principale)
# Heure - même datetime, Sage extrait la composante horaire
try: try:
doc.DO_Heure = pywintypes.Time(date_principale) doc.DO_Heure = pywintypes.Time(date_principale)
logger.debug( logger.debug(
@ -61,7 +57,6 @@ def creer_document_vente(
except Exception as e: except Exception as e:
logger.debug(f"DO_Heure non défini: {e}") logger.debug(f"DO_Heure non défini: {e}")
# Date secondaire (livraison, etc.)
if config.champ_date_secondaire and doc_data.get( if config.champ_date_secondaire and doc_data.get(
config.champ_date_secondaire config.champ_date_secondaire
): ):
@ -72,7 +67,6 @@ def creer_document_vente(
f"{config.champ_date_secondaire}: {doc_data[config.champ_date_secondaire]}" f"{config.champ_date_secondaire}: {doc_data[config.champ_date_secondaire]}"
) )
# ===== CLIENT =====
factory_client = self.cial.CptaApplication.FactoryClient factory_client = self.cial.CptaApplication.FactoryClient
persist_client = factory_client.ReadNumero(doc_data["client"]["code"]) persist_client = factory_client.ReadNumero(doc_data["client"]["code"])
@ -87,7 +81,6 @@ def creer_document_vente(
doc.Write() doc.Write()
logger.info(f"✓ Client {doc_data['client']['code']} associé") logger.info(f"✓ Client {doc_data['client']['code']} associé")
# ===== RÉFÉRENCE =====
if doc_data.get("reference"): if doc_data.get("reference"):
try: try:
doc.DO_Ref = doc_data["reference"] doc.DO_Ref = doc_data["reference"]
@ -95,11 +88,9 @@ def creer_document_vente(
except Exception as e: except Exception as e:
logger.warning(f"Référence non définie: {e}") logger.warning(f"Référence non définie: {e}")
# ===== CONFIGURATION SPÉCIFIQUE FACTURE =====
if type_document == TypeDocumentVente.FACTURE: if type_document == TypeDocumentVente.FACTURE:
_configurer_facture(self, doc) _configurer_facture(self, doc)
# ===== FACTORY LIGNES =====
try: try:
factory_lignes = doc.FactoryDocumentLigne factory_lignes = doc.FactoryDocumentLigne
except Exception: except Exception:
@ -109,7 +100,6 @@ def creer_document_vente(
logger.info(f"📦 Ajout de {len(doc_data['lignes'])} lignes...") logger.info(f"📦 Ajout de {len(doc_data['lignes'])} lignes...")
# ===== TRAITEMENT DES LIGNES =====
for idx, ligne_data in enumerate(doc_data["lignes"], 1): for idx, ligne_data in enumerate(doc_data["lignes"], 1):
_ajouter_ligne_document( _ajouter_ligne_document(
cial=self.cial, cial=self.cial,
@ -120,10 +110,8 @@ def creer_document_vente(
doc=doc, doc=doc,
) )
# ===== VALIDATION =====
logger.info("💾 Validation du document...") logger.info("💾 Validation du document...")
# Pour les factures, réassocier le client avant validation
if type_document == TypeDocumentVente.FACTURE: if type_document == TypeDocumentVente.FACTURE:
try: try:
doc.SetClient(client_obj) doc.SetClient(client_obj)
@ -136,7 +124,6 @@ def creer_document_vente(
doc.Write() doc.Write()
# Process() sauf pour devis en brouillon
if type_document != TypeDocumentVente.DEVIS: if type_document != TypeDocumentVente.DEVIS:
process.Process() process.Process()
logger.info("✓ Process() appelé") logger.info("✓ Process() appelé")
@ -147,7 +134,6 @@ def creer_document_vente(
except Exception: except Exception:
logger.debug(" ↳ Process() ignoré pour devis brouillon") logger.debug(" ↳ Process() ignoré pour devis brouillon")
# Commit transaction
if transaction_active: if transaction_active:
try: try:
self.cial.CptaApplication.CommitTrans() self.cial.CptaApplication.CommitTrans()
@ -157,7 +143,6 @@ def creer_document_vente(
time.sleep(2) time.sleep(2)
# ===== RÉCUPÉRATION DU NUMÉRO =====
numero_document = _recuperer_numero_document(process, doc) numero_document = _recuperer_numero_document(process, doc)
if not numero_document: if not numero_document:
@ -167,7 +152,6 @@ def creer_document_vente(
logger.info(f"📄 Numéro: {numero_document}") logger.info(f"📄 Numéro: {numero_document}")
# ===== RELECTURE POUR TOTAUX =====
doc_final_data = _relire_document_final( doc_final_data = _relire_document_final(
self, self,
config=config, config=config,
@ -205,21 +189,17 @@ def _appliquer_remise_ligne(ligne_obj, remise_pourcent: float) -> bool:
dispatch = ligne_obj._oleobj_ dispatch = ligne_obj._oleobj_
# 1. Récupérer l'objet Remise
dispid = dispatch.GetIDsOfNames(0, "Remise") dispid = dispatch.GetIDsOfNames(0, "Remise")
remise_obj = dispatch.Invoke(dispid, 0, pythoncom.DISPATCH_PROPERTYGET, 1) remise_obj = dispatch.Invoke(dispid, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
remise_wrapper = win32com.client.Dispatch(remise_obj) remise_wrapper = win32com.client.Dispatch(remise_obj)
# 2. Définir la remise via FromString
remise_wrapper.FromString(f"{remise_pourcent}%") remise_wrapper.FromString(f"{remise_pourcent}%")
# 3. Calcul (optionnel mais recommandé)
try: try:
remise_wrapper.Calcul() remise_wrapper.Calcul()
except Exception: except Exception:
pass pass
# 4. Write la ligne
ligne_obj.Write() ligne_obj.Write()
logger.info(f" Remise {remise_pourcent}% appliquée") logger.info(f" Remise {remise_pourcent}% appliquée")
@ -236,7 +216,6 @@ def _ajouter_ligne_document(
"""VERSION FINALE AVEC REMISES FONCTIONNELLES""" """VERSION FINALE AVEC REMISES FONCTIONNELLES"""
logger.info(f" ├─ Ligne {idx}: {ligne_data['article_code']}") logger.info(f" ├─ Ligne {idx}: {ligne_data['article_code']}")
# ===== CRÉATION LIGNE =====
persist_article = factory_article.ReadReference(ligne_data["article_code"]) persist_article = factory_article.ReadReference(ligne_data["article_code"])
if not persist_article: if not persist_article:
raise ValueError(f"Article {ligne_data['article_code']} introuvable") raise ValueError(f"Article {ligne_data['article_code']} introuvable")
@ -255,7 +234,6 @@ def _ajouter_ligne_document(
quantite = float(ligne_data["quantite"]) quantite = float(ligne_data["quantite"])
# ===== ASSOCIATION ARTICLE =====
try: try:
ligne_obj.SetDefaultArticleReference(ligne_data["article_code"], quantite) ligne_obj.SetDefaultArticleReference(ligne_data["article_code"], quantite)
except Exception: except Exception:
@ -265,7 +243,6 @@ def _ajouter_ligne_document(
ligne_obj.DL_Design = designation_sage ligne_obj.DL_Design = designation_sage
ligne_obj.DL_Qte = quantite ligne_obj.DL_Qte = quantite
# ===== PRIX =====
prix_auto = float(getattr(ligne_obj, "DL_PrixUnitaire", 0.0)) prix_auto = float(getattr(ligne_obj, "DL_PrixUnitaire", 0.0))
prix_perso = ligne_data.get("prix_unitaire_ht") prix_perso = ligne_data.get("prix_unitaire_ht")
@ -277,10 +254,8 @@ def _ajouter_ligne_document(
prix_final = float(getattr(ligne_obj, "DL_PrixUnitaire", 0)) prix_final = float(getattr(ligne_obj, "DL_PrixUnitaire", 0))
logger.info(f" 💰 Prix: {prix_final}") logger.info(f" 💰 Prix: {prix_final}")
# ===== WRITE INITIAL =====
ligne_obj.Write() ligne_obj.Write()
# ===== APPLICATION REMISE (TOUTES LES LIGNES!) =====
remise = ligne_data.get("remise_pourcentage", 0) remise = ligne_data.get("remise_pourcentage", 0)
if remise and remise > 0: if remise and remise > 0:
logger.info(f" 🎯 Application remise {remise}%...") logger.info(f" 🎯 Application remise {remise}%...")
@ -364,7 +339,6 @@ def _relire_document_final(
total_ttc = float(getattr(doc_final, "DO_TotalTTC", 0.0)) total_ttc = float(getattr(doc_final, "DO_TotalTTC", 0.0))
reference_finale = getattr(doc_final, "DO_Ref", "") reference_finale = getattr(doc_final, "DO_Ref", "")
# Récupérer le client depuis le document Sage
try: try:
client_obj = getattr(doc_final, "Client", None) client_obj = getattr(doc_final, "Client", None)
if client_obj: if client_obj:
@ -373,7 +347,6 @@ def _relire_document_final(
except Exception: except Exception:
pass pass
# Date secondaire
if config.champ_date_secondaire: if config.champ_date_secondaire:
try: try:
date_livr = getattr(doc_final, "DO_DateLivr", None) date_livr = getattr(doc_final, "DO_DateLivr", None)
@ -382,17 +355,14 @@ def _relire_document_final(
except Exception: except Exception:
pass pass
else: else:
# Valeurs par défaut si relecture échoue
total_ht = 0.0 total_ht = 0.0
total_ttc = 0.0 total_ttc = 0.0
reference_finale = doc_data.get("reference", "") reference_finale = doc_data.get("reference", "")
date_secondaire_value = doc_data.get(config.champ_date_secondaire) date_secondaire_value = doc_data.get(config.champ_date_secondaire)
# Fallback pour le code client (priorité: Sage > fallback > doc_data)
if not client_code: if not client_code:
client_code = client_code_fallback or doc_data.get("client", {}).get("code", "") client_code = client_code_fallback or doc_data.get("client", {}).get("code", "")
# Construction du résultat
resultat = { resultat = {
config.champ_numero: numero_document, config.champ_numero: numero_document,
"total_ht": total_ht, "total_ht": total_ht,
@ -407,7 +377,6 @@ def _relire_document_final(
"reference": reference_finale, "reference": reference_finale,
} }
# Ajout date secondaire si applicable
if config.champ_date_secondaire: if config.champ_date_secondaire:
resultat[config.champ_date_secondaire] = date_secondaire_value resultat[config.champ_date_secondaire] = date_secondaire_value
@ -447,7 +416,6 @@ def modifier_document_vente(
doc = win32com.client.CastTo(persist, "IBODocumentVente3") doc = win32com.client.CastTo(persist, "IBODocumentVente3")
doc.Read() doc.Read()
# Vérifications
statut_actuel = getattr(doc, "DO_Statut", 0) statut_actuel = getattr(doc, "DO_Statut", 0)
if statut_actuel == 5: if statut_actuel == 5:
@ -506,7 +474,6 @@ def modifier_document_vente(
logger.info(f" Référence: {modif_ref}") logger.info(f" Référence: {modif_ref}")
logger.info(f" Lignes: {modif_lignes}") logger.info(f" Lignes: {modif_lignes}")
# Reporter référence et statut après les lignes
doc_data_temp = doc_data.copy() doc_data_temp = doc_data.copy()
reference_a_modifier = None reference_a_modifier = None
statut_a_modifier = None statut_a_modifier = None
@ -567,7 +534,6 @@ def modifier_document_vente(
elif modif_lignes: elif modif_lignes:
logger.info("🔄 REMPLACEMENT COMPLET DES LIGNES...") logger.info("🔄 REMPLACEMENT COMPLET DES LIGNES...")
# Dates
if modif_date: if modif_date:
doc.DO_Date = pywintypes.Time( doc.DO_Date = pywintypes.Time(
normaliser_date(doc_data_temp.get(config.champ_date_principale)) normaliser_date(doc_data_temp.get(config.champ_date_principale))
@ -580,7 +546,6 @@ def modifier_document_vente(
) )
champs_modifies.append(config.champ_date_secondaire) champs_modifies.append(config.champ_date_secondaire)
# 🔥 CONFIGURATION SPÉCIFIQUE FACTURE (avant lignes)
if type_document == TypeDocumentVente.FACTURE: if type_document == TypeDocumentVente.FACTURE:
_configurer_facture(self, doc) _configurer_facture(self, doc)
@ -596,7 +561,6 @@ def modifier_document_vente(
factory_article = self.cial.FactoryArticle factory_article = self.cial.FactoryArticle
# Suppression lignes existantes
if nb_lignes_initial > 0: if nb_lignes_initial > 0:
logger.info(f" 🗑️ Suppression {nb_lignes_initial} lignes...") logger.info(f" 🗑️ Suppression {nb_lignes_initial} lignes...")
for idx in range(nb_lignes_initial, 0, -1): for idx in range(nb_lignes_initial, 0, -1):
@ -612,10 +576,8 @@ def modifier_document_vente(
logger.warning(f" Ligne {idx}: {e}") logger.warning(f" Ligne {idx}: {e}")
logger.info(" ✓ Lignes supprimées") logger.info(" ✓ Lignes supprimées")
# Ajout nouvelles lignes avec REMISES
logger.info(f" Ajout {nb_nouvelles} lignes...") logger.info(f" Ajout {nb_nouvelles} lignes...")
for idx, ligne_data in enumerate(nouvelles_lignes, 1): for idx, ligne_data in enumerate(nouvelles_lignes, 1):
# 🔥 UTILISE _ajouter_ligne_document qui applique les remises
_ajouter_ligne_document( _ajouter_ligne_document(
cial=self.cial, cial=self.cial,
factory_lignes=factory_lignes, factory_lignes=factory_lignes,

View file

@ -167,10 +167,8 @@ def _parser_heure_sage(do_heure) -> str:
return "00:00:00" return "00:00:00"
try: try:
# Convertir en entier pour éliminer les zéros de padding SQL
heure_int = int(str(do_heure).strip()) heure_int = int(str(do_heure).strip())
# Formatter en string 6 caractères (HHMMSS)
heure_str = str(heure_int).zfill(6) heure_str = str(heure_int).zfill(6)
hh = int(heure_str[0:2]) hh = int(heure_str[0:2])

View file

@ -80,7 +80,6 @@ def contact_to_dict(row) -> Dict:
def _collaborators_to_dict(row) -> Optional[dict]: def _collaborators_to_dict(row) -> Optional[dict]:
"""Convertit une ligne SQL en dictionnaire collaborateur""" """Convertit une ligne SQL en dictionnaire collaborateur"""
# Vérifier si le collaborateur existe
if not hasattr(row, "Collab_CO_No") or row.Collab_CO_No is None: if not hasattr(row, "Collab_CO_No") or row.Collab_CO_No is None:
return None return None
@ -157,7 +156,6 @@ def collaborators_to_dict(row):
def tiers_to_dict(row) -> dict: def tiers_to_dict(row) -> dict:
"""Convertit une ligne SQL en dictionnaire tiers""" """Convertit une ligne SQL en dictionnaire tiers"""
tiers = { tiers = {
# IDENTIFICATION
"numero": _safe_strip(row.CT_Num), "numero": _safe_strip(row.CT_Num),
"intitule": _safe_strip(row.CT_Intitule), "intitule": _safe_strip(row.CT_Intitule),
"type_tiers": row.CT_Type, "type_tiers": row.CT_Type,
@ -167,7 +165,6 @@ def tiers_to_dict(row) -> dict:
"siret": _safe_strip(row.CT_Siret), "siret": _safe_strip(row.CT_Siret),
"tva_intra": _safe_strip(row.CT_Identifiant), "tva_intra": _safe_strip(row.CT_Identifiant),
"code_naf": _safe_strip(row.CT_Ape), "code_naf": _safe_strip(row.CT_Ape),
# ADRESSE
"contact": _safe_strip(row.CT_Contact), "contact": _safe_strip(row.CT_Contact),
"adresse": _safe_strip(row.CT_Adresse), "adresse": _safe_strip(row.CT_Adresse),
"complement": _safe_strip(row.CT_Complement), "complement": _safe_strip(row.CT_Complement),
@ -175,19 +172,16 @@ def tiers_to_dict(row) -> dict:
"ville": _safe_strip(row.CT_Ville), "ville": _safe_strip(row.CT_Ville),
"region": _safe_strip(row.CT_CodeRegion), "region": _safe_strip(row.CT_CodeRegion),
"pays": _safe_strip(row.CT_Pays), "pays": _safe_strip(row.CT_Pays),
# TELECOM
"telephone": _safe_strip(row.CT_Telephone), "telephone": _safe_strip(row.CT_Telephone),
"telecopie": _safe_strip(row.CT_Telecopie), "telecopie": _safe_strip(row.CT_Telecopie),
"email": _safe_strip(row.CT_EMail), "email": _safe_strip(row.CT_EMail),
"site_web": _safe_strip(row.CT_Site), "site_web": _safe_strip(row.CT_Site),
"facebook": _safe_strip(row.CT_Facebook), "facebook": _safe_strip(row.CT_Facebook),
"linkedin": _safe_strip(row.CT_LinkedIn), "linkedin": _safe_strip(row.CT_LinkedIn),
# TAUX
"taux01": row.CT_Taux01, "taux01": row.CT_Taux01,
"taux02": row.CT_Taux02, "taux02": row.CT_Taux02,
"taux03": row.CT_Taux03, "taux03": row.CT_Taux03,
"taux04": row.CT_Taux04, "taux04": row.CT_Taux04,
# STATISTIQUES
"statistique01": _safe_strip(row.CT_Statistique01), "statistique01": _safe_strip(row.CT_Statistique01),
"statistique02": _safe_strip(row.CT_Statistique02), "statistique02": _safe_strip(row.CT_Statistique02),
"statistique03": _safe_strip(row.CT_Statistique03), "statistique03": _safe_strip(row.CT_Statistique03),
@ -198,13 +192,11 @@ def tiers_to_dict(row) -> dict:
"statistique08": _safe_strip(row.CT_Statistique08), "statistique08": _safe_strip(row.CT_Statistique08),
"statistique09": _safe_strip(row.CT_Statistique09), "statistique09": _safe_strip(row.CT_Statistique09),
"statistique10": _safe_strip(row.CT_Statistique10), "statistique10": _safe_strip(row.CT_Statistique10),
# COMMERCIAL
"encours_autorise": row.CT_Encours, "encours_autorise": row.CT_Encours,
"assurance_credit": row.CT_Assurance, "assurance_credit": row.CT_Assurance,
"langue": row.CT_Langue, "langue": row.CT_Langue,
"commercial_code": row.CO_No, "commercial_code": row.CO_No,
"commercial": _collaborators_to_dict(row), "commercial": _collaborators_to_dict(row),
# FACTURATION
"lettrage_auto": (row.CT_Lettrage == 1), "lettrage_auto": (row.CT_Lettrage == 1),
"est_actif": (row.CT_Sommeil == 0), "est_actif": (row.CT_Sommeil == 0),
"type_facture": row.CT_Facture, "type_facture": row.CT_Facture,
@ -216,16 +208,12 @@ def tiers_to_dict(row) -> dict:
"exclure_relance": (row.CT_NotRappel == 1), "exclure_relance": (row.CT_NotRappel == 1),
"exclure_penalites": (row.CT_NotPenal == 1), "exclure_penalites": (row.CT_NotPenal == 1),
"bon_a_payer": row.CT_BonAPayer, "bon_a_payer": row.CT_BonAPayer,
# LOGISTIQUE
"priorite_livraison": row.CT_PrioriteLivr, "priorite_livraison": row.CT_PrioriteLivr,
"livraison_partielle": row.CT_LivrPartielle, "livraison_partielle": row.CT_LivrPartielle,
"delai_transport": row.CT_DelaiTransport, "delai_transport": row.CT_DelaiTransport,
"delai_appro": row.CT_DelaiAppro, "delai_appro": row.CT_DelaiAppro,
# COMMENTAIRE
"commentaire": _safe_strip(row.CT_Commentaire), "commentaire": _safe_strip(row.CT_Commentaire),
# ANALYTIQUE
"section_analytique": _safe_strip(row.CA_Num), "section_analytique": _safe_strip(row.CA_Num),
# ORGANISATION / SURVEILLANCE
"mode_reglement_code": row.MR_No, "mode_reglement_code": row.MR_No,
"surveillance_active": (row.CT_Surveillance == 1), "surveillance_active": (row.CT_Surveillance == 1),
"coface": _safe_strip(row.CT_Coface), "coface": _safe_strip(row.CT_Coface),
@ -236,7 +224,6 @@ def tiers_to_dict(row) -> dict:
"sv_objet_maj": _safe_strip(row.CT_SvObjetMaj), "sv_objet_maj": _safe_strip(row.CT_SvObjetMaj),
"sv_chiffre_affaires": row.CT_SvCA, "sv_chiffre_affaires": row.CT_SvCA,
"sv_resultat": row.CT_SvResultat, "sv_resultat": row.CT_SvResultat,
# COMPTE GENERAL ET CATEGORIES
"compte_general": _safe_strip(row.CG_NumPrinc), "compte_general": _safe_strip(row.CG_NumPrinc),
"categorie_tarif": row.N_CatTarif, "categorie_tarif": row.N_CatTarif,
"categorie_compta": row.N_CatCompta, "categorie_compta": row.N_CatCompta,