Cleaned projects' files
This commit is contained in:
parent
9ffad8287d
commit
31dec46226
9 changed files with 1 additions and 267 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -34,4 +34,4 @@ htmlcov/
|
|||
dist/
|
||||
|
||||
|
||||
cleaner.py
|
||||
*clean*.py
|
||||
6
main.py
6
main.py
|
|
@ -1680,7 +1680,6 @@ def get_tous_journaux():
|
|||
try:
|
||||
journaux = sage.lire_tous_journaux()
|
||||
|
||||
# Grouper par type
|
||||
par_type = {}
|
||||
for j in journaux:
|
||||
t = j["type_libelle"]
|
||||
|
|
@ -1745,7 +1744,6 @@ def introspection_com():
|
|||
}
|
||||
|
||||
with sage._com_context(), sage._lock_com:
|
||||
# Attributs de cial
|
||||
try:
|
||||
for attr in dir(sage.cial):
|
||||
if not attr.startswith("_"):
|
||||
|
|
@ -1753,7 +1751,6 @@ def introspection_com():
|
|||
except Exception as e:
|
||||
resultats["cial_error"] = str(e)
|
||||
|
||||
# Attributs de BaseCpta
|
||||
try:
|
||||
base_cpta = sage.cial.BaseCpta
|
||||
for attr in dir(base_cpta):
|
||||
|
|
@ -1762,14 +1759,12 @@ def introspection_com():
|
|||
except Exception as e:
|
||||
resultats["base_cpta_error"] = str(e)
|
||||
|
||||
# Attributs de ParametreDossier
|
||||
try:
|
||||
param = sage.cial.BaseCpta.ParametreDossier
|
||||
for attr in dir(param):
|
||||
if not attr.startswith("_"):
|
||||
resultats["param_dossier_attributes"].append(attr)
|
||||
|
||||
# Tester spécifiquement les attributs logo possibles
|
||||
resultats["logo_tests"] = {}
|
||||
for logo_attr in [
|
||||
"Logo",
|
||||
|
|
@ -1842,7 +1837,6 @@ def get_tous_reglements(
|
|||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
# Route: Détail d'un règlement
|
||||
@app.get("/sage/reglements/facture/{facture_no}", dependencies=[Depends(verify_token)])
|
||||
def get_reglement_facture_detail(facture_no):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1003,12 +1003,10 @@ class SageConnector:
|
|||
with self._get_sql_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# === DÉTECTION DES COLONNES ===
|
||||
logger.info(f"[SQL] Lecture article {reference}...")
|
||||
cursor.execute("SELECT TOP 1 * FROM F_ARTICLE")
|
||||
colonnes_disponibles = [column[0] for column in cursor.description]
|
||||
|
||||
# Configuration du mapping complet
|
||||
colonnes_config = {
|
||||
"AR_Ref": "reference",
|
||||
"AR_Design": "designation",
|
||||
|
|
@ -1090,7 +1088,6 @@ class SageConnector:
|
|||
"AR_Exclure": "exclure",
|
||||
}
|
||||
|
||||
# Sélection des colonnes disponibles
|
||||
colonnes_a_lire = [
|
||||
col_sql
|
||||
for col_sql in colonnes_config.keys()
|
||||
|
|
@ -1101,7 +1098,6 @@ class SageConnector:
|
|||
logger.error("[SQL] Aucune colonne mappée trouvée !")
|
||||
return None
|
||||
|
||||
# Construction de la requête SQL avec échappement des noms de colonnes
|
||||
colonnes_sql = []
|
||||
for col in colonnes_a_lire:
|
||||
if " " in col or "/" in col or "è" in col:
|
||||
|
|
@ -1120,7 +1116,6 @@ class SageConnector:
|
|||
logger.info(f"[SQL] Article {reference} non trouvé")
|
||||
return None
|
||||
|
||||
# Construction du dictionnaire row_data
|
||||
row_data = {}
|
||||
for idx, col_sql in enumerate(colonnes_a_lire):
|
||||
valeur = row[idx]
|
||||
|
|
@ -1128,10 +1123,8 @@ class SageConnector:
|
|||
valeur = valeur.strip()
|
||||
row_data[col_sql] = valeur
|
||||
|
||||
# Mapping de l'article
|
||||
article = _mapper_article_depuis_row(row_data, colonnes_config)
|
||||
|
||||
# Enrichissements
|
||||
articles = [
|
||||
article
|
||||
] # Liste d'un seul article pour les fonctions d'enrichissement
|
||||
|
|
@ -1876,7 +1869,6 @@ class SageConnector:
|
|||
persist_tiers = None
|
||||
type_tiers = None
|
||||
|
||||
# Tentative 1 : Client
|
||||
try:
|
||||
logger.info(" Recherche dans Clients...")
|
||||
persist_tiers = factory_client.ReadNumero(numero_client)
|
||||
|
|
@ -1886,7 +1878,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.debug(f" Pas trouvé comme Client: {e}")
|
||||
|
||||
# Tentative 2 : Fournisseur (si pas trouvé comme client)
|
||||
if not persist_tiers:
|
||||
try:
|
||||
logger.info(" Recherche dans Fournisseurs...")
|
||||
|
|
@ -1897,7 +1888,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.debug(f" Pas trouvé comme Fournisseur: {e}")
|
||||
|
||||
# Vérification finale
|
||||
if not persist_tiers:
|
||||
raise ValueError(
|
||||
f"Le tiers '{numero_client}' est introuvable dans Sage 100c. "
|
||||
|
|
@ -1905,7 +1895,6 @@ class SageConnector:
|
|||
f"(Client ou Fournisseur)."
|
||||
)
|
||||
|
||||
# Cast et lecture
|
||||
try:
|
||||
client_obj = win32com.client.CastTo(persist_tiers, "IBOClient3")
|
||||
client_obj.Read()
|
||||
|
|
@ -2348,7 +2337,6 @@ class SageConnector:
|
|||
persist_tiers = None
|
||||
type_tiers = None
|
||||
|
||||
# Tentative 1 : Client
|
||||
try:
|
||||
logger.info(" Recherche dans Clients...")
|
||||
persist_tiers = factory_client.ReadNumero(numero)
|
||||
|
|
@ -2358,7 +2346,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.debug(f" Pas trouvé comme Client: {e}")
|
||||
|
||||
# Tentative 2 : Fournisseur (si pas trouvé comme client)
|
||||
if not persist_tiers:
|
||||
try:
|
||||
logger.info(" Recherche dans Fournisseurs...")
|
||||
|
|
@ -2369,7 +2356,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.debug(f" Pas trouvé comme Fournisseur: {e}")
|
||||
|
||||
# Vérification finale
|
||||
if not persist_tiers:
|
||||
raise ValueError(
|
||||
f"Le tiers '{numero}' est introuvable dans Sage 100c. "
|
||||
|
|
@ -2377,7 +2363,6 @@ class SageConnector:
|
|||
f"(Client ou Fournisseur)."
|
||||
)
|
||||
|
||||
# Cast et lecture
|
||||
try:
|
||||
client_obj = win32com.client.CastTo(persist_tiers, "IBOClient3")
|
||||
client_obj.Read()
|
||||
|
|
@ -3719,7 +3704,6 @@ class SageConnector:
|
|||
f" [DEBUG] Méthodes disponibles sur client: {methodes_client}"
|
||||
)
|
||||
|
||||
# Chercher spécifiquement les méthodes de verrouillage
|
||||
lock_methods = [
|
||||
m
|
||||
for m in methodes_client
|
||||
|
|
@ -3736,7 +3720,6 @@ class SageConnector:
|
|||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
# Approche 1: ReadLock (méthode préférée)
|
||||
if hasattr(client, "ReadLock"):
|
||||
client.ReadLock()
|
||||
locked = True
|
||||
|
|
@ -3744,7 +3727,6 @@ class SageConnector:
|
|||
logger.info(" Verrouillage via ReadLock() [OK]")
|
||||
break
|
||||
|
||||
# Approche 2: Lock
|
||||
elif hasattr(client, "Lock"):
|
||||
client.Lock()
|
||||
locked = True
|
||||
|
|
@ -3752,7 +3734,6 @@ class SageConnector:
|
|||
logger.info(" Verrouillage via Lock() [OK]")
|
||||
break
|
||||
|
||||
# Approche 3: LockRecord
|
||||
elif hasattr(client, "LockRecord"):
|
||||
client.LockRecord()
|
||||
locked = True
|
||||
|
|
@ -3760,7 +3741,6 @@ class SageConnector:
|
|||
logger.info(" Verrouillage via LockRecord() [OK]")
|
||||
break
|
||||
|
||||
# Approche 4: Read avec paramètre mode écriture
|
||||
else:
|
||||
try:
|
||||
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."
|
||||
)
|
||||
else:
|
||||
# Autre erreur, propager
|
||||
raise
|
||||
|
||||
logger.info(
|
||||
|
|
@ -4366,7 +4345,6 @@ class SageConnector:
|
|||
|
||||
if not champs_modifies:
|
||||
logger.warning("Aucun champ à modifier")
|
||||
# Déverrouiller si nécessaire
|
||||
if locked:
|
||||
try:
|
||||
if hasattr(client, "ReadUnlock"):
|
||||
|
|
@ -4399,7 +4377,6 @@ class SageConnector:
|
|||
logger.error(f"[ERREUR] {error_detail}")
|
||||
raise RuntimeError(f"Echec Write(): {error_detail}")
|
||||
finally:
|
||||
# Toujours déverrouiller après Write (succès ou échec)
|
||||
if locked:
|
||||
try:
|
||||
if hasattr(client, "ReadUnlock"):
|
||||
|
|
@ -4414,7 +4391,6 @@ class SageConnector:
|
|||
except Exception as unlock_err:
|
||||
logger.warning(f" Déverrouillage ignoré: {unlock_err}")
|
||||
|
||||
# Relire après Write pour retourner les données à jour
|
||||
client.Read()
|
||||
|
||||
logger.info("=" * 80)
|
||||
|
|
@ -4478,7 +4454,6 @@ class SageConnector:
|
|||
try:
|
||||
logger.info("[ARTICLE] === CREATION ARTICLE ===")
|
||||
|
||||
# === Validation données ===
|
||||
valide, erreur = valider_donnees_creation(article_data)
|
||||
if not valide:
|
||||
raise ValueError(erreur)
|
||||
|
|
@ -4492,7 +4467,6 @@ class SageConnector:
|
|||
logger.debug(f"BeginTrans non disponible : {e}")
|
||||
|
||||
try:
|
||||
# === Découverte dépôts ===
|
||||
depots_disponibles = []
|
||||
depot_a_utiliser = None
|
||||
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']})"
|
||||
)
|
||||
|
||||
# === Extraction et validation des données ===
|
||||
reference = article_data.get("reference", "").upper().strip()
|
||||
designation = article_data.get("designation", "").strip()
|
||||
if len(designation) > 69:
|
||||
|
|
@ -4569,7 +4542,6 @@ class SageConnector:
|
|||
logger.info(f"[ARTICLE] Référence : {reference}")
|
||||
logger.info(f"[ARTICLE] Désignation : {designation}")
|
||||
|
||||
# === Vérifier si article existe ===
|
||||
factory = self.cial.FactoryArticle
|
||||
try:
|
||||
article_existant = factory.ReadReference(reference)
|
||||
|
|
@ -4583,7 +4555,6 @@ class SageConnector:
|
|||
):
|
||||
raise
|
||||
|
||||
# === Créer l'article ===
|
||||
persist = factory.Create()
|
||||
article = win32com.client.CastTo(persist, "IBOArticle3")
|
||||
article.SetDefault()
|
||||
|
|
@ -4591,7 +4562,6 @@ class SageConnector:
|
|||
article.AR_Ref = reference
|
||||
article.AR_Design = designation
|
||||
|
||||
# === Recherche article modèle ===
|
||||
logger.info("[MODELE] Recherche article modèle...")
|
||||
article_modele_ref = None
|
||||
article_modele = None
|
||||
|
|
@ -4634,7 +4604,6 @@ class SageConnector:
|
|||
"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...")
|
||||
unite_trouvee = False
|
||||
try:
|
||||
|
|
@ -4651,7 +4620,6 @@ class SageConnector:
|
|||
"Impossible de copier l'unité depuis le modèle"
|
||||
)
|
||||
|
||||
# === Gestion famille ===
|
||||
famille_trouvee = False
|
||||
famille_code_personnalise = article_data.get("famille")
|
||||
|
||||
|
|
@ -4736,7 +4704,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.debug(f" Famille non copiable : {e}")
|
||||
|
||||
# === Champs obligatoires depuis modèle ===
|
||||
logger.info("[CHAMPS] Copie champs obligatoires...")
|
||||
article.AR_Type = int(getattr(article_modele, "AR_Type", 0))
|
||||
article.AR_Nature = int(getattr(article_modele, "AR_Nature", 0))
|
||||
|
|
@ -4744,12 +4711,10 @@ class SageConnector:
|
|||
article.AR_SuiviStock = 2
|
||||
logger.info(" [OK] Champs de base copiés (AR_SuiviStock=2)")
|
||||
|
||||
# === Application des champs fournis ===
|
||||
logger.info("[CHAMPS] Application champs fournis...")
|
||||
champs_appliques = []
|
||||
champs_echoues = []
|
||||
|
||||
# Prix de vente
|
||||
if "prix_vente" in article_data:
|
||||
try:
|
||||
article.AR_PrixVen = float(article_data["prix_vente"])
|
||||
|
|
@ -4760,7 +4725,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"prix_vente: {e}")
|
||||
|
||||
# Prix d'achat
|
||||
if "prix_achat" in article_data:
|
||||
try:
|
||||
article.AR_PrixAchat = float(article_data["prix_achat"])
|
||||
|
|
@ -4771,7 +4735,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"prix_achat: {e}")
|
||||
|
||||
# Coefficient
|
||||
if "coef" in article_data:
|
||||
try:
|
||||
article.AR_Coef = float(article_data["coef"])
|
||||
|
|
@ -4780,7 +4743,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"coef: {e}")
|
||||
|
||||
# Code EAN
|
||||
if "code_ean" in article_data:
|
||||
try:
|
||||
article.AR_CodeBarre = str(article_data["code_ean"])
|
||||
|
|
@ -4789,7 +4751,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"code_ean: {e}")
|
||||
|
||||
# Description -> AR_Langue1
|
||||
if "description" in article_data:
|
||||
try:
|
||||
article.AR_Langue1 = str(article_data["description"])[:255]
|
||||
|
|
@ -4798,7 +4759,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"description: {e}")
|
||||
|
||||
# Pays
|
||||
if "pays" in article_data:
|
||||
try:
|
||||
article.AR_Pays = str(article_data["pays"])[:3].upper()
|
||||
|
|
@ -4807,7 +4767,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"pays: {e}")
|
||||
|
||||
# Garantie
|
||||
if "garantie" in article_data:
|
||||
try:
|
||||
article.AR_Garantie = int(article_data["garantie"])
|
||||
|
|
@ -4816,7 +4775,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"garantie: {e}")
|
||||
|
||||
# Délai
|
||||
if "delai" in article_data:
|
||||
try:
|
||||
article.AR_Delai = int(article_data["delai"])
|
||||
|
|
@ -4825,7 +4783,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"delai: {e}")
|
||||
|
||||
# Poids net
|
||||
if "poids_net" in article_data:
|
||||
try:
|
||||
article.AR_PoidsNet = float(article_data["poids_net"])
|
||||
|
|
@ -4834,7 +4791,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"poids_net: {e}")
|
||||
|
||||
# Poids brut
|
||||
if "poids_brut" in article_data:
|
||||
try:
|
||||
article.AR_PoidsBrut = float(article_data["poids_brut"])
|
||||
|
|
@ -4845,7 +4801,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"poids_brut: {e}")
|
||||
|
||||
# Code fiscal
|
||||
if "code_fiscal" in article_data:
|
||||
try:
|
||||
article.AR_CodeFiscal = str(article_data["code_fiscal"])[
|
||||
|
|
@ -4858,7 +4813,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"code_fiscal: {e}")
|
||||
|
||||
# Soumis escompte
|
||||
if "soumis_escompte" in article_data:
|
||||
try:
|
||||
article.AR_Escompte = (
|
||||
|
|
@ -4871,7 +4825,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"soumis_escompte: {e}")
|
||||
|
||||
# Publié
|
||||
if "publie" in article_data:
|
||||
try:
|
||||
article.AR_Publie = 1 if article_data["publie"] else 0
|
||||
|
|
@ -4880,7 +4833,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
champs_echoues.append(f"publie: {e}")
|
||||
|
||||
# En sommeil
|
||||
if "en_sommeil" in article_data:
|
||||
try:
|
||||
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)}"
|
||||
)
|
||||
|
||||
# === Écriture dans Sage ===
|
||||
logger.info("[ARTICLE] Écriture dans Sage...")
|
||||
try:
|
||||
article.Write()
|
||||
|
|
@ -4916,7 +4867,6 @@ class SageConnector:
|
|||
logger.error(f" [ERREUR] Write() : {error_detail}")
|
||||
raise RuntimeError(f"Échec création : {error_detail}")
|
||||
|
||||
# === Statistiques (AR_Stat après Write) ===
|
||||
stats_a_definir = []
|
||||
for i in range(1, 6):
|
||||
stat_key = f"stat_0{i}"
|
||||
|
|
@ -4938,7 +4888,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f" ⚠ Statistiques : {e}")
|
||||
|
||||
# === Commit transaction ===
|
||||
if transaction_active:
|
||||
try:
|
||||
self.cial.CptaApplication.CommitTrans()
|
||||
|
|
@ -4946,7 +4895,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f"[COMMIT] Erreur : {e}")
|
||||
|
||||
# === Gestion stocks ===
|
||||
stock_defini = False
|
||||
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']}')..."
|
||||
)
|
||||
|
||||
# Méthode 1 : Créer via COM
|
||||
if stock_reel:
|
||||
try:
|
||||
depot_obj = depot_a_utiliser["objet"]
|
||||
|
|
@ -5004,7 +4951,6 @@ class SageConnector:
|
|||
except Exception as 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:
|
||||
try:
|
||||
with self._get_sql_connection() as conn:
|
||||
|
|
@ -5079,13 +5025,11 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.error(f"[STOCK] Erreur SQL : {e}")
|
||||
|
||||
# === Construction réponse depuis SQL ===
|
||||
logger.info("[RESPONSE] Construction réponse depuis SQL...")
|
||||
try:
|
||||
with self._get_sql_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Lecture complète article
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT
|
||||
|
|
@ -5161,7 +5105,6 @@ class SageConnector:
|
|||
else None,
|
||||
}
|
||||
|
||||
# Lecture stocks
|
||||
cursor.execute(
|
||||
"""
|
||||
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:
|
||||
logger.warning(f"[RESPONSE] Erreur lecture SQL : {e}")
|
||||
|
||||
# Fallback sur extraction COM si SQL échoue
|
||||
logger.info("[FALLBACK] Extraction COM...")
|
||||
article_cree_persist = factory.ReadReference(reference)
|
||||
if not article_cree_persist:
|
||||
|
|
@ -5227,7 +5169,6 @@ class SageConnector:
|
|||
if not resultat:
|
||||
resultat = {"reference": reference, "designation": designation}
|
||||
|
||||
# Forcer les valeurs connues
|
||||
for key in [
|
||||
"prix_vente",
|
||||
"prix_achat",
|
||||
|
|
@ -5296,7 +5237,6 @@ class SageConnector:
|
|||
champs_modifies = []
|
||||
champs_echoues = []
|
||||
|
||||
# === Gestion famille ===
|
||||
if "famille" in article_data and article_data["famille"]:
|
||||
famille_code_demande = article_data["famille"].upper().strip()
|
||||
logger.info(f"[FAMILLE] Changement : {famille_code_demande}")
|
||||
|
|
@ -5369,7 +5309,6 @@ class SageConnector:
|
|||
logger.error(f" [ERREUR] Famille : {e}")
|
||||
champs_echoues.append(f"famille: {e}")
|
||||
|
||||
# === Traitement explicite des champs ===
|
||||
if "designation" in article_data:
|
||||
try:
|
||||
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("[ARTICLE] Écriture...")
|
||||
|
||||
# === Écriture COM ===
|
||||
try:
|
||||
article.Write()
|
||||
logger.info("[ARTICLE] Write() réussi")
|
||||
|
|
@ -5528,7 +5466,6 @@ class SageConnector:
|
|||
logger.error(f"[ARTICLE] Erreur Write() : {error_detail}")
|
||||
raise RuntimeError(f"Échec modification : {error_detail}")
|
||||
|
||||
# === Statistiques (AR_Stat après Write) ===
|
||||
stats_a_modifier = []
|
||||
for i in range(1, 6):
|
||||
stat_key = f"stat_0{i}"
|
||||
|
|
@ -5555,7 +5492,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f" ⚠ Statistiques : {e}")
|
||||
|
||||
# === Gestion stocks mini/maxi via SQL ===
|
||||
if "stock_mini" in article_data or "stock_maxi" in article_data:
|
||||
try:
|
||||
with self._get_sql_connection() as conn:
|
||||
|
|
@ -5595,13 +5531,11 @@ class SageConnector:
|
|||
logger.error(f"[STOCK] Erreur SQL : {e}")
|
||||
champs_echoues.append(f"stocks: {e}")
|
||||
|
||||
# === Construction réponse depuis SQL ===
|
||||
logger.info("[RESPONSE] Construction réponse depuis SQL...")
|
||||
try:
|
||||
with self._get_sql_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Lecture complète article
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT
|
||||
|
|
@ -5667,7 +5601,6 @@ class SageConnector:
|
|||
"stat_05": _safe_strip(row[24]) if row[24] else None,
|
||||
}
|
||||
|
||||
# Lecture stocks
|
||||
cursor.execute(
|
||||
"""
|
||||
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:
|
||||
logger.warning(f"[RESPONSE] Erreur lecture SQL : {e}")
|
||||
|
||||
# Fallback sur extraction COM
|
||||
article.Read()
|
||||
logger.info(
|
||||
f"[ARTICLE] MODIFIÉ : {reference} ({len(champs_modifies)} champs)"
|
||||
|
|
@ -7495,7 +7427,6 @@ class SageConnector:
|
|||
cursor.execute(query, params)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
# ⚠️⚠️⚠️ VÉRIFIE CETTE LIGNE ⚠️⚠️⚠️
|
||||
collaborateurs = [collaborators_to_dict(row) for row in rows]
|
||||
|
||||
logger.info(f"✓ SQL: {len(collaborateurs)} collaborateurs")
|
||||
|
|
@ -7530,7 +7461,6 @@ class SageConnector:
|
|||
if not row:
|
||||
return None
|
||||
|
||||
# ⚠️ UTILISER LA FONCTION DE CLASSE EXISTANTE
|
||||
collaborateur = collaborators_to_dict(row)
|
||||
|
||||
logger.info(
|
||||
|
|
@ -7547,7 +7477,6 @@ class SageConnector:
|
|||
if not self.cial:
|
||||
raise RuntimeError("Connexion Sage non établie")
|
||||
|
||||
# Validation préalable
|
||||
if not data.get("nom"):
|
||||
raise ValueError("Le champ 'nom' est obligatoire")
|
||||
|
||||
|
|
@ -7560,7 +7489,6 @@ class SageConnector:
|
|||
|
||||
try:
|
||||
with self._com_context(), self._lock_com:
|
||||
# ===== VÉRIFICATION DOUBLON VIA SQL =====
|
||||
logger.info("🔍 Vérification doublon...")
|
||||
with self._get_sql_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
|
@ -7575,7 +7503,6 @@ class SageConnector:
|
|||
)
|
||||
logger.info("✓ Pas de doublon")
|
||||
|
||||
# ===== FACTORY + CREATE =====
|
||||
try:
|
||||
factory = self.cial.FactoryCollaborateur
|
||||
except AttributeError:
|
||||
|
|
@ -7583,7 +7510,6 @@ class SageConnector:
|
|||
|
||||
persist = factory.Create()
|
||||
|
||||
# Cast vers interface
|
||||
collab = None
|
||||
for iface in [
|
||||
"IBOCollaborateur3",
|
||||
|
|
@ -7599,14 +7525,12 @@ class SageConnector:
|
|||
if not collab:
|
||||
collab = persist
|
||||
|
||||
# ===== SETDEFAULT =====
|
||||
try:
|
||||
collab.SetDefault()
|
||||
logger.info("✓ SetDefault()")
|
||||
except Exception as e:
|
||||
logger.warning(f"SetDefault() ignoré: {e}")
|
||||
|
||||
# ===== HELPER =====
|
||||
def safe_set(obj, attr, value, max_len=None):
|
||||
"""Affecte une valeur de manière sécurisée"""
|
||||
if value is None or value == "":
|
||||
|
|
@ -7622,13 +7546,10 @@ class SageConnector:
|
|||
logger.warning(f" ✗ {attr}: {e}")
|
||||
return False
|
||||
|
||||
# ===== CHAMPS DIRECTS SUR COLLABORATEUR =====
|
||||
logger.info("📝 Champs directs...")
|
||||
|
||||
# Obligatoire
|
||||
safe_set(collab, "Nom", nom_upper, 35)
|
||||
|
||||
# Optionnels
|
||||
safe_set(collab, "Prenom", prenom, 35)
|
||||
safe_set(collab, "Fonction", data.get("fonction"), 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, "Skype", data.get("skype"), 35)
|
||||
|
||||
# ===== SOUS-OBJET ADRESSE =====
|
||||
logger.info("📍 Adresse...")
|
||||
try:
|
||||
adresse_obj = collab.Adresse
|
||||
|
|
@ -7650,7 +7570,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f"⚠️ Erreur Adresse: {e}")
|
||||
|
||||
# ===== SOUS-OBJET TELECOM =====
|
||||
logger.info("📞 Telecom...")
|
||||
try:
|
||||
telecom_obj = collab.Telecom
|
||||
|
|
@ -7661,7 +7580,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f"⚠️ Erreur Telecom: {e}")
|
||||
|
||||
# ===== CHAMPS BOOLÉENS (seulement si True) =====
|
||||
logger.info("🔘 Booléens...")
|
||||
if data.get("vendeur") is True:
|
||||
try:
|
||||
|
|
@ -7690,7 +7608,6 @@ class SageConnector:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# ===== WRITE =====
|
||||
logger.info("💾 Write()...")
|
||||
try:
|
||||
collab.Write()
|
||||
|
|
@ -7699,10 +7616,8 @@ class SageConnector:
|
|||
logger.error(f" Write() échoué: {e}")
|
||||
raise RuntimeError(f"Échec Write(): {e}")
|
||||
|
||||
# ===== RÉCUPÉRATION DU NUMÉRO =====
|
||||
numero_cree = None
|
||||
|
||||
# Via Read()
|
||||
try:
|
||||
collab.Read()
|
||||
for attr in ["No", "CO_No", "Numero"]:
|
||||
|
|
@ -7716,7 +7631,6 @@ class SageConnector:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Via SQL si pas trouvé
|
||||
if not numero_cree:
|
||||
try:
|
||||
with self._get_sql_connection() as conn:
|
||||
|
|
@ -7737,7 +7651,6 @@ class SageConnector:
|
|||
)
|
||||
logger.info(f"{'=' * 70}")
|
||||
|
||||
# Retourner le collaborateur
|
||||
if numero_cree:
|
||||
return self.lire_collaborateur(numero_cree)
|
||||
else:
|
||||
|
|
@ -7761,13 +7674,11 @@ class SageConnector:
|
|||
|
||||
try:
|
||||
with self._com_context(), self._lock_com:
|
||||
# ===== LECTURE DU COLLABORATEUR EXISTANT =====
|
||||
try:
|
||||
factory = self.cial.FactoryCollaborateur
|
||||
except AttributeError:
|
||||
factory = self.cial.CptaApplication.FactoryCollaborateur
|
||||
|
||||
# Lire par numéro
|
||||
try:
|
||||
persist = factory.ReadNumero(numero)
|
||||
except Exception as e:
|
||||
|
|
@ -7776,7 +7687,6 @@ class SageConnector:
|
|||
if not persist:
|
||||
raise ValueError(f"Collaborateur {numero} introuvable")
|
||||
|
||||
# Cast vers interface
|
||||
collab = None
|
||||
for iface in [
|
||||
"IBOCollaborateur3",
|
||||
|
|
@ -7792,14 +7702,12 @@ class SageConnector:
|
|||
if not collab:
|
||||
collab = persist
|
||||
|
||||
# Charger les données actuelles
|
||||
try:
|
||||
collab.Read()
|
||||
logger.info(f"✓ Collaborateur {numero} chargé")
|
||||
except Exception as e:
|
||||
logger.warning(f"Read() ignoré: {e}")
|
||||
|
||||
# ===== HELPER =====
|
||||
def safe_set(obj, attr, value, max_len=None):
|
||||
"""Affecte une valeur de manière sécurisée"""
|
||||
if value is None:
|
||||
|
|
@ -7817,7 +7725,6 @@ class SageConnector:
|
|||
|
||||
champs_modifies = []
|
||||
|
||||
# ===== CHAMPS DIRECTS SUR COLLABORATEUR =====
|
||||
logger.info("📝 Champs directs...")
|
||||
|
||||
champs_directs = {
|
||||
|
|
@ -7834,13 +7741,11 @@ class SageConnector:
|
|||
for py_field, (sage_attr, max_len) in champs_directs.items():
|
||||
if py_field in data:
|
||||
val = data[py_field]
|
||||
# Cas spécial: nom en majuscules
|
||||
if py_field == "nom" and val:
|
||||
val = str(val).upper().strip()
|
||||
if safe_set(collab, sage_attr, val, max_len):
|
||||
champs_modifies.append(sage_attr)
|
||||
|
||||
# ===== SOUS-OBJET ADRESSE =====
|
||||
logger.info("📍 Adresse...")
|
||||
try:
|
||||
adresse_obj = collab.Adresse
|
||||
|
|
@ -7864,7 +7769,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f"⚠️ Erreur accès Adresse: {e}")
|
||||
|
||||
# ===== SOUS-OBJET TELECOM =====
|
||||
logger.info("📞 Telecom...")
|
||||
try:
|
||||
telecom_obj = collab.Telecom
|
||||
|
|
@ -7886,7 +7790,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f"⚠️ Erreur accès Telecom: {e}")
|
||||
|
||||
# ===== CHAMPS BOOLÉENS =====
|
||||
logger.info("🔘 Booléens...")
|
||||
|
||||
champs_bool = {
|
||||
|
|
@ -7907,7 +7810,6 @@ class SageConnector:
|
|||
except Exception as e:
|
||||
logger.warning(f" ✗ {sage_attr}: {e}")
|
||||
|
||||
# ===== VÉRIFICATION =====
|
||||
if not champs_modifies:
|
||||
logger.info("ℹ️ Aucun champ à modifier")
|
||||
return self.lire_collaborateur(numero)
|
||||
|
|
@ -7916,7 +7818,6 @@ class SageConnector:
|
|||
f"📋 {len(champs_modifies)} champ(s) à modifier: {champs_modifies}"
|
||||
)
|
||||
|
||||
# ===== WRITE =====
|
||||
logger.info("💾 Write()...")
|
||||
try:
|
||||
collab.Write()
|
||||
|
|
@ -7925,7 +7826,6 @@ class SageConnector:
|
|||
logger.error(f" Write() échoué: {e}")
|
||||
raise RuntimeError(f"Échec Write(): {e}")
|
||||
|
||||
# ===== RETOUR =====
|
||||
logger.info(f"\n{'=' * 70}")
|
||||
logger.info(f" COLLABORATEUR MODIFIÉ: N°{numero}")
|
||||
logger.info(f"{'=' * 70}")
|
||||
|
|
@ -7952,7 +7852,6 @@ class SageConnector:
|
|||
societe = society_to_dict(row)
|
||||
societe["exercices"] = build_exercices(row)
|
||||
|
||||
# Stocker le numéro de dossier pour la recherche du logo
|
||||
self._numero_dossier = societe.get("numero_dossier")
|
||||
|
||||
add_logo(societe)
|
||||
|
|
|
|||
|
|
@ -948,7 +948,6 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
|
||||
return val
|
||||
|
||||
# === CHAMPS DE BASE ===
|
||||
article["reference"] = get_val("AR_Ref", convert_type=str)
|
||||
article["designation"] = get_val("AR_Design", 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["raccourci"] = get_val("AR_Raccourci", convert_type=str)
|
||||
|
||||
# === PRIX ===
|
||||
article["prix_vente"] = get_val("AR_PrixVen", 0.0, float)
|
||||
article["prix_achat"] = get_val("AR_PrixAch", 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)
|
||||
|
||||
# === UNITÉS ET POIDS (avec normalisation string) ===
|
||||
article["unite_vente"] = normalize_string_field(get_val("AR_UniteVen"))
|
||||
article["unite_poids"] = normalize_string_field(get_val("AR_UnitePoids"))
|
||||
article["poids_net"] = get_val("AR_PoidsNet", 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_2"] = normalize_string_field(get_val("AR_Gamme2"))
|
||||
|
||||
# === TYPE ARTICLE (avec libellé) ===
|
||||
type_val = get_val("AR_Type", 0, int)
|
||||
article["type_article"] = type_val
|
||||
article["type_article_libelle"] = TypeArticle.get_label(type_val)
|
||||
|
||||
# === FAMILLE ===
|
||||
article["famille_code"] = get_val("FA_CodeFamille", convert_type=str)
|
||||
|
||||
# === NATURE ET GARANTIE ===
|
||||
article["nature"] = get_val("AR_Nature", 0, int)
|
||||
article["garantie"] = get_val("AR_Garantie", 0, int)
|
||||
article["code_fiscal"] = normalize_string_field(get_val("AR_CodeFiscal"))
|
||||
article["pays"] = normalize_string_field(get_val("AR_Pays"))
|
||||
|
||||
# === FOURNISSEUR ===
|
||||
article["fournisseur_principal"] = get_val("CO_No", 0, int)
|
||||
|
||||
# === CONDITIONNEMENT (avec normalisation string) ===
|
||||
article["conditionnement"] = normalize_string_field(get_val("AR_Condition"))
|
||||
article["nb_colis"] = get_val("AR_NbColis", 0, int)
|
||||
article["prevision"] = get_val("AR_Prevision", False, bool)
|
||||
|
||||
# === SUIVI STOCK (avec libellé) ===
|
||||
suivi_stock_val = normalize_enum_to_int(get_val("AR_SuiviStock"))
|
||||
article["suivi_stock"] = 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"))
|
||||
article["nomenclature"] = 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_operatoire"] = get_val("AR_QteOperatoire", 0.0, float)
|
||||
|
||||
# === STATUT ARTICLE ===
|
||||
sommeil = get_val("AR_Sommeil", 0, int)
|
||||
article["est_actif"] = sommeil == 0
|
||||
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["delai"] = get_val("AR_Delai", 0, int)
|
||||
|
||||
# === STATISTIQUES ===
|
||||
article["stat_01"] = get_val("AR_Stat01", convert_type=str)
|
||||
article["stat_02"] = get_val("AR_Stat02", 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["hors_statistique"] = get_val("AR_HorsStat", False, bool)
|
||||
|
||||
# === CATÉGORIES COMPTABLES ===
|
||||
article["categorie_1"] = get_val("CL_No1", 0, int)
|
||||
article["categorie_2"] = get_val("CL_No2", 0, int)
|
||||
article["categorie_3"] = get_val("CL_No3", 0, int)
|
||||
article["categorie_4"] = get_val("CL_No4", 0, int)
|
||||
|
||||
# === DATE MODIFICATION ===
|
||||
date_modif = get_val("AR_DateModif")
|
||||
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["non_imprimable"] = get_val("AR_NotImp", 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["criticite"] = get_val("AR_Criticite", 0, int)
|
||||
|
||||
# === PARAMÈTRES DE PRODUCTION ===
|
||||
article["reprise_code_defaut"] = normalize_string_field(get_val("RP_CodeDefaut"))
|
||||
article["delai_fabrication"] = get_val("AR_DelaiFabrication", 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["cycle"] = get_val("AR_Cycle", 1, int)
|
||||
|
||||
# === MÉDIA ET LANGUES ===
|
||||
article["photo"] = get_val("AR_Photo", convert_type=str)
|
||||
article["langue_1"] = get_val("AR_Langue1", convert_type=str)
|
||||
article["langue_2"] = get_val("AR_Langue2", convert_type=str)
|
||||
|
||||
# === FRAIS ===
|
||||
article["frais_01_denomination"] = get_val(
|
||||
"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
|
||||
)
|
||||
|
||||
# === CHAMPS PERSONNALISÉS ===
|
||||
article["marque_commerciale"] = get_val("Marque commerciale", convert_type=str)
|
||||
|
||||
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["exclure"] = get_val("AR_Exclure", False, bool)
|
||||
|
||||
# === INITIALISATION DES CHAMPS DE STOCK (remplis par enrichissement) ===
|
||||
article["stock_reel"] = 0.0
|
||||
article["stock_mini"] = 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_disponible"] = 0.0
|
||||
|
||||
# === INITIALISATION DES CHAMPS DE FAMILLE (remplis par enrichissement) ===
|
||||
article["famille_libelle"] = None
|
||||
article["famille_type"] = 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_pays"] = None
|
||||
|
||||
# === INITIALISATION DES CHAMPS FOURNISSEUR/TVA (remplis par enrichissement) ===
|
||||
article["fournisseur_nom"] = None
|
||||
article["tva_code"] = None
|
||||
article["tva_taux"] = None
|
||||
|
|
@ -1257,7 +1234,6 @@ def enrichir_fournisseurs_articles(articles: List[Dict], cursor) -> List[Dict]:
|
|||
nb_enrichis = 0
|
||||
for article in articles:
|
||||
num_fourn = article.get("fournisseur_principal")
|
||||
# Convertir en string pour correspondre au fournisseur_map
|
||||
num_fourn_str = (
|
||||
str(num_fourn).strip() if num_fourn not in (None, "", " ") else None
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ def _get_journal_auto(self, mode_reglement: int) -> str:
|
|||
with self._get_sql_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Mode Espèces = 2 (selon l'image Sage fournie)
|
||||
if mode_reglement == 2: # Espèces
|
||||
cursor.execute("""
|
||||
SELECT TOP 1 JO_Num
|
||||
|
|
@ -25,7 +24,6 @@ def _get_journal_auto(self, mode_reglement: int) -> str:
|
|||
ORDER BY JO_Num
|
||||
""")
|
||||
else:
|
||||
# Autres modes → Banque
|
||||
cursor.execute("""
|
||||
SELECT TOP 1 JO_Num
|
||||
FROM F_JOURNAUX
|
||||
|
|
@ -40,7 +38,6 @@ def _get_journal_auto(self, mode_reglement: int) -> str:
|
|||
if row:
|
||||
return row[0].strip()
|
||||
|
||||
# Fallback: premier journal de trésorerie disponible
|
||||
cursor.execute("""
|
||||
SELECT TOP 1 JO_Num
|
||||
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()
|
||||
|
||||
# Mode Espèces (2) doit utiliser un journal caisse (53x)
|
||||
if mode_reglement == 2:
|
||||
if not compte_general.startswith("53"):
|
||||
logger.warning(
|
||||
f"Mode Espèces avec journal non-caisse ({code_journal}, compte {compte_general})"
|
||||
)
|
||||
else:
|
||||
# Autres modes doivent utiliser un journal banque (51x)
|
||||
if compte_general.startswith("53"):
|
||||
logger.warning(
|
||||
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:
|
||||
return (row[0] or "").strip()
|
||||
|
||||
# Fallback sur les libellés standards
|
||||
libelles = {
|
||||
0: "Chèque",
|
||||
1: "Virement",
|
||||
|
|
@ -319,7 +313,6 @@ def lire_tous_reglements(
|
|||
|
||||
facture = _format_facture(row, echeances)
|
||||
|
||||
# Filtrer par statut si demandé
|
||||
if statut_reglement:
|
||||
reste = facture["montants"]["reste_a_regler"]
|
||||
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
|
||||
)
|
||||
|
||||
# Utiliser la même structure que lire_tous_reglements
|
||||
# mais avec les infos complètes du client
|
||||
types_doc = {6: "Facture", 7: "Avoir"}
|
||||
montant_regle = sum(e["montant_regle"] for e in echeances)
|
||||
reste_a_regler = montant_ttc - montant_regle
|
||||
|
|
@ -607,7 +598,6 @@ def lire_reglement_detail(self, rg_no: int) -> Dict:
|
|||
if not row:
|
||||
raise ValueError(f"Règlement {rg_no} introuvable")
|
||||
|
||||
# Récupérer les imputations
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT
|
||||
|
|
@ -766,11 +756,9 @@ def regler_facture(
|
|||
|
||||
date_reglement = date_reglement or datetime.now()
|
||||
|
||||
# Déduction automatique du journal si non fourni
|
||||
if not code_journal:
|
||||
code_journal = _get_journal_auto(self, mode_reglement)
|
||||
else:
|
||||
# Valider la cohérence journal/mode
|
||||
_valider_coherence_journal_mode(self, code_journal, mode_reglement)
|
||||
|
||||
logger.info(
|
||||
|
|
@ -801,7 +789,6 @@ def regler_facture(
|
|||
f"Montant ({montant}€) supérieur au solde ({solde_actuel:.2f}€)"
|
||||
)
|
||||
|
||||
# Récupérer le client
|
||||
client_code = ""
|
||||
try:
|
||||
client_obj = getattr(doc, "Client", None)
|
||||
|
|
@ -811,12 +798,10 @@ def regler_facture(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Récupérer l'échéance
|
||||
echeance = _get_premiere_echeance(doc)
|
||||
if not echeance:
|
||||
raise ValueError(f"Facture {numero_facture} sans échéance")
|
||||
|
||||
# Exécuter le règlement
|
||||
numero_reglement = _executer_reglement_com(
|
||||
self,
|
||||
doc=doc,
|
||||
|
|
@ -847,7 +832,6 @@ def regler_facture(
|
|||
nouveau_solde = total_ttc - nouveau_montant_regle
|
||||
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)
|
||||
|
||||
return {
|
||||
|
|
@ -916,17 +900,14 @@ def _executer_reglement_com(
|
|||
):
|
||||
erreurs = []
|
||||
|
||||
# APPROCHE PRINCIPALE: Créer règlement complet, l'écrire, puis l'assigner au process
|
||||
try:
|
||||
logger.info("Création du règlement via FactoryDocumentReglement...")
|
||||
|
||||
# 1. Créer le règlement
|
||||
factory_reg = self.cial.FactoryDocumentReglement
|
||||
reg = factory_reg.Create()
|
||||
reg = win32com.client.CastTo(reg, "IBODocumentReglement")
|
||||
logger.info(" Règlement créé et casté vers IBODocumentReglement")
|
||||
|
||||
# 2. Configurer le Journal (objet)
|
||||
try:
|
||||
journal_factory = self.cial.CptaApplication.FactoryJournal
|
||||
journal_persist = journal_factory.ReadNumero(code_journal)
|
||||
|
|
@ -936,7 +917,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.warning(f" Journal: {e}")
|
||||
|
||||
# 3. Configurer le TiersPayeur (objet client)
|
||||
try:
|
||||
factory_client = self.cial.CptaApplication.FactoryClient
|
||||
if client_code:
|
||||
|
|
@ -947,7 +927,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.warning(f" TiersPayeur: {e}")
|
||||
|
||||
# 4. Configurer les champs simples
|
||||
try:
|
||||
reg.RG_Date = pywintypes.Time(date_reglement)
|
||||
logger.info(f" RG_Date: {date_reglement}")
|
||||
|
|
@ -960,7 +939,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.warning(f" RG_Montant: {e}")
|
||||
|
||||
# 5. Mode de règlement via l'objet Reglement
|
||||
try:
|
||||
mode_factory = getattr(
|
||||
self.cial.CptaApplication, "FactoryModeReglement", None
|
||||
|
|
@ -973,7 +951,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.debug(f" Mode règlement via factory: {e}")
|
||||
|
||||
# 6. Devise
|
||||
if devise_code != 0:
|
||||
try:
|
||||
reg.RG_Devise = devise_code
|
||||
|
|
@ -987,7 +964,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.debug(f" RG_Cours: {e}")
|
||||
|
||||
# Montant en devise
|
||||
try:
|
||||
montant_devise = montant * cours_devise
|
||||
reg.RG_MontantDev = montant_devise
|
||||
|
|
@ -995,7 +971,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.debug(f" RG_MontantDev: {e}")
|
||||
|
||||
# 7. TVA sur encaissement
|
||||
if tva_encaissement:
|
||||
try:
|
||||
reg.RG_Encaissement = 1
|
||||
|
|
@ -1003,7 +978,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.debug(f" RG_Encaissement: {e}")
|
||||
|
||||
# 8. Compte général spécifique
|
||||
if compte_general:
|
||||
try:
|
||||
cg_factory = self.cial.CptaApplication.FactoryCompteG
|
||||
|
|
@ -1014,7 +988,6 @@ def _executer_reglement_com(
|
|||
except Exception as e:
|
||||
logger.debug(f" CompteG: {e}")
|
||||
|
||||
# 9. Référence et libellé
|
||||
if reference:
|
||||
try:
|
||||
reg.RG_Reference = reference
|
||||
|
|
@ -1039,12 +1012,10 @@ def _executer_reglement_com(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# 10. ÉCRIRE le règlement
|
||||
reg.Write()
|
||||
numero = getattr(reg, "RG_Piece", None)
|
||||
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:
|
||||
logger.info(" Création du lien règlement-échéance...")
|
||||
factory_reg_ech = getattr(reg, "FactoryDocumentReglementEcheance", None)
|
||||
|
|
@ -1052,7 +1023,6 @@ def _executer_reglement_com(
|
|||
if factory_reg_ech:
|
||||
reg_ech = factory_reg_ech.Create()
|
||||
|
||||
# Cast vers IBODocumentReglementEcheance
|
||||
for iface in [
|
||||
"IBODocumentReglementEcheance3",
|
||||
"IBODocumentReglementEcheance",
|
||||
|
|
@ -1064,21 +1034,18 @@ def _executer_reglement_com(
|
|||
except Exception:
|
||||
continue
|
||||
|
||||
# Définir l'échéance
|
||||
try:
|
||||
reg_ech.Echeance = echeance
|
||||
logger.info(" Echeance définie")
|
||||
except Exception as e:
|
||||
logger.warning(f" Echeance: {e}")
|
||||
|
||||
# Définir le montant
|
||||
try:
|
||||
reg_ech.RC_Montant = montant
|
||||
logger.info(f" RC_Montant: {montant}")
|
||||
except Exception as e:
|
||||
logger.warning(f" RC_Montant: {e}")
|
||||
|
||||
# Écrire le lien
|
||||
try:
|
||||
reg_ech.SetDefault()
|
||||
except Exception:
|
||||
|
|
@ -1093,16 +1060,13 @@ def _executer_reglement_com(
|
|||
erreurs.append(f"Lien échéance: {e}")
|
||||
logger.warning(f" Erreur création lien: {e}")
|
||||
|
||||
# Si le lien a échoué, essayer via le process
|
||||
logger.info(" Tentative via CreateProcess_ReglerEcheances...")
|
||||
try:
|
||||
process = self.cial.CreateProcess_ReglerEcheances()
|
||||
|
||||
# Assigner le règlement déjà écrit
|
||||
process.Reglement = reg
|
||||
logger.info(" Règlement assigné au process")
|
||||
|
||||
# Ajouter l'échéance
|
||||
try:
|
||||
process.AddDocumentEcheanceMontant(echeance, montant)
|
||||
logger.info(" Échéance ajoutée avec montant")
|
||||
|
|
@ -1134,7 +1098,6 @@ def _executer_reglement_com(
|
|||
erreurs.append(f"FactoryDocumentReglement: {e}")
|
||||
logger.error(f"FactoryDocumentReglement échoué: {e}")
|
||||
|
||||
# APPROCHE ALTERNATIVE: Via le mode règlement de l'échéance
|
||||
try:
|
||||
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 = win32com.client.CastTo(new_reg, "IBODocumentReglement")
|
||||
|
||||
# Configurer
|
||||
journal_factory = self.cial.CptaApplication.FactoryJournal
|
||||
journal_persist = journal_factory.ReadNumero(code_journal)
|
||||
if journal_persist:
|
||||
|
|
@ -1181,7 +1143,6 @@ def _executer_reglement_com(
|
|||
new_reg.RG_Montant = montant
|
||||
new_reg.RG_Impute = 1
|
||||
|
||||
# Devise si non EUR
|
||||
if devise_code != 0:
|
||||
try:
|
||||
new_reg.RG_Devise = devise_code
|
||||
|
|
@ -1190,14 +1151,12 @@ def _executer_reglement_com(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# TVA encaissement
|
||||
if tva_encaissement:
|
||||
try:
|
||||
new_reg.RG_Encaissement = 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Compte général
|
||||
if compte_general:
|
||||
try:
|
||||
cg_factory = self.cial.CptaApplication.FactoryCompteG
|
||||
|
|
@ -1239,7 +1198,6 @@ def introspecter_reglement(self):
|
|||
result = {}
|
||||
try:
|
||||
with self._com_context(), self._lock_com:
|
||||
# IBODocumentReglement et sa factory de liens
|
||||
try:
|
||||
factory = self.cial.FactoryDocumentReglement
|
||||
reg = factory.Create()
|
||||
|
|
@ -1248,7 +1206,6 @@ def introspecter_reglement(self):
|
|||
a for a in dir(reg) if not a.startswith("_")
|
||||
]
|
||||
|
||||
# FactoryDocumentReglementEcheance depuis le règlement
|
||||
factory_lien = getattr(reg, "FactoryDocumentReglementEcheance", None)
|
||||
if factory_lien:
|
||||
lien = factory_lien.Create()
|
||||
|
|
@ -1271,14 +1228,12 @@ def introspecter_reglement(self):
|
|||
except Exception as e:
|
||||
result["error_reglement"] = str(e)
|
||||
|
||||
# Process
|
||||
try:
|
||||
process = self.cial.CreateProcess_ReglerEcheances()
|
||||
result["Process"] = [a for a in dir(process) if not a.startswith("_")]
|
||||
except Exception as e:
|
||||
result["error_process"] = str(e)
|
||||
|
||||
# Échéance et ses attributs
|
||||
try:
|
||||
factory_doc = self.cial.FactoryDocumentVente
|
||||
doc_list = factory_doc.List
|
||||
|
|
@ -1306,7 +1261,6 @@ def introspecter_reglement(self):
|
|||
if not a.startswith("_")
|
||||
]
|
||||
|
||||
# FactoryDocumentReglementEcheance depuis l'échéance
|
||||
factory_lien_ech = getattr(
|
||||
ech,
|
||||
"FactoryDocumentReglementEcheance",
|
||||
|
|
@ -1342,7 +1296,6 @@ def introspecter_reglement(self):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Reglement de l'échéance (mode)
|
||||
mode = getattr(ech, "Reglement", None)
|
||||
if mode:
|
||||
result["Echeance_Reglement_mode"] = [
|
||||
|
|
@ -1382,7 +1335,6 @@ def regler_factures_client(
|
|||
|
||||
date_reglement = date_reglement or datetime.now()
|
||||
|
||||
# Déduction automatique du journal si non fourni
|
||||
if not code_journal:
|
||||
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:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Vérifier d'abord si F_DEVISE existe
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_NAME = 'F_DEVISE'
|
||||
|
|
@ -1640,17 +1591,14 @@ def lire_devises(self) -> List[Dict]:
|
|||
if devises:
|
||||
return devises
|
||||
|
||||
# Fallback: Lire depuis P_DOSSIER
|
||||
cursor.execute("""
|
||||
SELECT N_DeviseCompte, N_DeviseEquival
|
||||
FROM P_DOSSIER
|
||||
""")
|
||||
row = cursor.fetchone()
|
||||
|
||||
# Devise par défaut basée sur la config dossier
|
||||
devise_principale = row[0] if row else 0
|
||||
|
||||
# Retourner les devises standards
|
||||
devises_standards = [
|
||||
{
|
||||
"code": 0,
|
||||
|
|
@ -1709,7 +1657,6 @@ def lire_journaux_tresorerie(self) -> List[Dict]:
|
|||
journaux = []
|
||||
for row in cursor.fetchall():
|
||||
compte_general = (row[2] or "").strip()
|
||||
# Déterminer le type basé sur le compte général
|
||||
if compte_general.startswith("53"):
|
||||
type_journal = "caisse"
|
||||
elif compte_general.startswith("51"):
|
||||
|
|
@ -1735,7 +1682,6 @@ def lire_comptes_generaux(
|
|||
if not self.cial:
|
||||
raise RuntimeError("Connexion Sage non établie")
|
||||
|
||||
# Mapping type -> préfixes de comptes
|
||||
prefixes_map = {
|
||||
"client": ["411"],
|
||||
"fournisseur": ["401"],
|
||||
|
|
@ -1763,7 +1709,6 @@ def lire_comptes_generaux(
|
|||
"""
|
||||
params = []
|
||||
|
||||
# Appliquer les filtres
|
||||
if type_compte and type_compte in prefixes_map:
|
||||
prefixes = prefixes_map[type_compte]
|
||||
conditions = " OR ".join(["CG_Num LIKE ?" for _ in prefixes])
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
|
||||
persist = factory.ReadPiece(60, numero_facture)
|
||||
|
||||
# 1. Attributs du persist brut
|
||||
persist_attrs = [a for a in dir(persist) if not a.startswith("_")]
|
||||
result["persist"]["all_attrs"] = persist_attrs
|
||||
result["persist"]["methods"] = []
|
||||
|
|
@ -127,7 +126,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
{"name": attr, "error": str(e)[:50]}
|
||||
)
|
||||
|
||||
# Chercher spécifiquement les attributs liés à validation/valide
|
||||
result["persist"]["validation_related"] = [
|
||||
a
|
||||
for a in persist_attrs
|
||||
|
|
@ -137,7 +135,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
)
|
||||
]
|
||||
|
||||
# 2. IBODocumentVente3
|
||||
try:
|
||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||
doc.Read()
|
||||
|
|
@ -147,7 +144,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
result["IBODocumentVente3"]["methods"] = []
|
||||
result["IBODocumentVente3"]["properties_with_values"] = []
|
||||
|
||||
# Lister les méthodes
|
||||
for attr in doc_attrs:
|
||||
try:
|
||||
val = getattr(doc, attr, None)
|
||||
|
|
@ -156,7 +152,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Chercher DO_* properties
|
||||
result["IBODocumentVente3"]["DO_properties"] = []
|
||||
for attr in doc_attrs:
|
||||
if attr.startswith("DO_"):
|
||||
|
|
@ -170,7 +165,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
{"name": attr, "error": str(e)[:50]}
|
||||
)
|
||||
|
||||
# Chercher les attributs liés à validation
|
||||
result["IBODocumentVente3"]["validation_related"] = [
|
||||
a
|
||||
for a in doc_attrs
|
||||
|
|
@ -183,7 +177,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
except Exception as e:
|
||||
result["IBODocumentVente3"]["error"] = str(e)
|
||||
|
||||
# 3. IBODocument3
|
||||
try:
|
||||
doc3 = win32com.client.CastTo(persist, "IBODocument3")
|
||||
doc3.Read()
|
||||
|
|
@ -202,7 +195,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
except Exception as e:
|
||||
result["IBODocument3"]["error"] = str(e)
|
||||
|
||||
# 4. IPMDocument
|
||||
try:
|
||||
pmdoc = win32com.client.CastTo(persist, "IPMDocument")
|
||||
|
||||
|
|
@ -215,7 +207,6 @@ def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|||
except Exception as e:
|
||||
result["IPMDocument"]["error"] = str(e)
|
||||
|
||||
# 5. Chercher FactoryDocument* sur le document
|
||||
result["factories_on_doc"] = []
|
||||
for attr in persist_attrs:
|
||||
if "Factory" in attr:
|
||||
|
|
@ -235,13 +226,11 @@ def introspecter_validation(connector, numero_facture: str = None) -> Dict:
|
|||
|
||||
try:
|
||||
with connector._com_context(), connector._lock_com:
|
||||
# Tous les CreateProcess
|
||||
cial_attrs = [a for a in dir(connector.cial) if not a.startswith("_")]
|
||||
result["all_createprocess"] = [
|
||||
a for a in cial_attrs if "CreateProcess" in a
|
||||
]
|
||||
|
||||
# Explorer chaque process
|
||||
for process_name in result["all_createprocess"]:
|
||||
try:
|
||||
process = getattr(connector.cial, process_name)()
|
||||
|
|
@ -255,7 +244,6 @@ def introspecter_validation(connector, numero_facture: str = None) -> Dict:
|
|||
except Exception as e:
|
||||
result[process_name] = {"error": str(e)}
|
||||
|
||||
# Introspection document si fourni
|
||||
if numero_facture:
|
||||
result["document"] = introspecter_document_complet(
|
||||
connector, numero_facture
|
||||
|
|
@ -270,11 +258,9 @@ def introspecter_validation(connector, numero_facture: str = None) -> Dict:
|
|||
def valider_facture(connector, numero_facture: str) -> Dict:
|
||||
logger.info(f" Validation facture {numero_facture} (SQL direct)")
|
||||
|
||||
# Vérifications préalables
|
||||
with connector._get_sql_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Vérifier que la facture existe et peut être validée
|
||||
cursor.execute(
|
||||
"""
|
||||
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é
|
||||
raise ValueError("Facture annulée, validation impossible")
|
||||
|
||||
# Valider via SQL
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE F_DOCENTETE
|
||||
|
|
@ -308,7 +293,6 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
|||
|
||||
conn.commit()
|
||||
|
||||
# Vérifier
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT DO_Valide, DO_Imprim
|
||||
|
|
@ -400,7 +384,6 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
|
|||
if not persist:
|
||||
raise ValueError(f"Impossible de lire la facture {numero_facture}")
|
||||
|
||||
# APPROCHE 1: Accès direct à DO_Valide sur IBODocumentVente3
|
||||
try:
|
||||
logger.info(
|
||||
" 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.Read()
|
||||
|
||||
# Vérifier la valeur actuelle
|
||||
valeur_avant = getattr(doc, "DO_Valide", None)
|
||||
logger.info(f" DO_Valide avant: {valeur_avant}")
|
||||
|
||||
# Tenter la modification
|
||||
doc.DO_Valide = valeur_cible
|
||||
doc.Write()
|
||||
|
||||
# Relire pour vérifier
|
||||
doc.Read()
|
||||
valeur_apres = getattr(doc, "DO_Valide", None)
|
||||
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}")
|
||||
logger.warning(f" Erreur: {e}")
|
||||
|
||||
# APPROCHE 2: Via IBODocument3 (interface parent)
|
||||
try:
|
||||
logger.info(" APPROCHE 2: Via 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:
|
||||
erreurs.append(f"IBODocument3: {e}")
|
||||
|
||||
# APPROCHE 3: Chercher un CreateProcess de validation
|
||||
try:
|
||||
logger.info(" APPROCHE 3: Recherche CreateProcess de validation...")
|
||||
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:
|
||||
try:
|
||||
process = getattr(connector.cial, proc_name)()
|
||||
# Lister les attributs du process
|
||||
proc_attrs = [a for a in dir(process) if not a.startswith("_")]
|
||||
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:
|
||||
erreurs.append(f"CreateProcess: {e}")
|
||||
|
||||
# APPROCHE 4: WriteDefault avec paramètres
|
||||
try:
|
||||
logger.info(" APPROCHE 4: WriteDefault...")
|
||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||
|
|
@ -510,7 +486,6 @@ def explorer_toutes_interfaces_validation(connector, numero_facture: str) -> Dic
|
|||
factory = connector.cial.FactoryDocumentVente
|
||||
persist = factory.ReadPiece(60, numero_facture)
|
||||
|
||||
# Liste des interfaces à tester
|
||||
interfaces = [
|
||||
"IBODocumentVente3",
|
||||
"IBODocument3",
|
||||
|
|
@ -544,7 +519,6 @@ def explorer_toutes_interfaces_validation(connector, numero_facture: str) -> Dic
|
|||
props[names[0]] = {
|
||||
"memid": func_desc.memid,
|
||||
"invkind": func_desc.invkind,
|
||||
# invkind: 1=METHOD, 2=GET, 4=PUT, 8=PUTREF
|
||||
"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:
|
||||
result["interfaces"][iface_name] = {"error": str(e)[:100]}
|
||||
|
||||
# Explorer aussi FactoryDocumentVente pour des méthodes de validation
|
||||
try:
|
||||
factory_attrs = [a for a in dir(factory) if not a.startswith("_")]
|
||||
result["factory_methods"] = [
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ def creer_document_vente(
|
|||
transaction_active = False
|
||||
|
||||
try:
|
||||
# Démarrage transaction
|
||||
try:
|
||||
self.cial.CptaApplication.BeginTrans()
|
||||
transaction_active = True
|
||||
|
|
@ -35,7 +34,6 @@ def creer_document_vente(
|
|||
except Exception as e:
|
||||
logger.warning(f"BeginTrans échoué (non critique): {e}")
|
||||
|
||||
# Création du document
|
||||
process = self.cial.CreateProcess_Document(config.type_sage)
|
||||
doc = process.Document
|
||||
|
||||
|
|
@ -46,13 +44,11 @@ def creer_document_vente(
|
|||
|
||||
logger.info(f"✓ Document {config.nom_document} créé")
|
||||
|
||||
# ===== DATES =====
|
||||
date_principale = normaliser_date(
|
||||
doc_data.get(config.champ_date_principale)
|
||||
)
|
||||
doc.DO_Date = pywintypes.Time(date_principale)
|
||||
|
||||
# Heure - même datetime, Sage extrait la composante horaire
|
||||
try:
|
||||
doc.DO_Heure = pywintypes.Time(date_principale)
|
||||
logger.debug(
|
||||
|
|
@ -61,7 +57,6 @@ def creer_document_vente(
|
|||
except Exception as e:
|
||||
logger.debug(f"DO_Heure non défini: {e}")
|
||||
|
||||
# Date secondaire (livraison, etc.)
|
||||
if config.champ_date_secondaire and doc_data.get(
|
||||
config.champ_date_secondaire
|
||||
):
|
||||
|
|
@ -72,7 +67,6 @@ def creer_document_vente(
|
|||
f"✓ {config.champ_date_secondaire}: {doc_data[config.champ_date_secondaire]}"
|
||||
)
|
||||
|
||||
# ===== CLIENT =====
|
||||
factory_client = self.cial.CptaApplication.FactoryClient
|
||||
persist_client = factory_client.ReadNumero(doc_data["client"]["code"])
|
||||
|
||||
|
|
@ -87,7 +81,6 @@ def creer_document_vente(
|
|||
doc.Write()
|
||||
logger.info(f"✓ Client {doc_data['client']['code']} associé")
|
||||
|
||||
# ===== RÉFÉRENCE =====
|
||||
if doc_data.get("reference"):
|
||||
try:
|
||||
doc.DO_Ref = doc_data["reference"]
|
||||
|
|
@ -95,11 +88,9 @@ def creer_document_vente(
|
|||
except Exception as e:
|
||||
logger.warning(f"Référence non définie: {e}")
|
||||
|
||||
# ===== CONFIGURATION SPÉCIFIQUE FACTURE =====
|
||||
if type_document == TypeDocumentVente.FACTURE:
|
||||
_configurer_facture(self, doc)
|
||||
|
||||
# ===== FACTORY LIGNES =====
|
||||
try:
|
||||
factory_lignes = doc.FactoryDocumentLigne
|
||||
except Exception:
|
||||
|
|
@ -109,7 +100,6 @@ def creer_document_vente(
|
|||
|
||||
logger.info(f"📦 Ajout de {len(doc_data['lignes'])} lignes...")
|
||||
|
||||
# ===== TRAITEMENT DES LIGNES =====
|
||||
for idx, ligne_data in enumerate(doc_data["lignes"], 1):
|
||||
_ajouter_ligne_document(
|
||||
cial=self.cial,
|
||||
|
|
@ -120,10 +110,8 @@ def creer_document_vente(
|
|||
doc=doc,
|
||||
)
|
||||
|
||||
# ===== VALIDATION =====
|
||||
logger.info("💾 Validation du document...")
|
||||
|
||||
# Pour les factures, réassocier le client avant validation
|
||||
if type_document == TypeDocumentVente.FACTURE:
|
||||
try:
|
||||
doc.SetClient(client_obj)
|
||||
|
|
@ -136,7 +124,6 @@ def creer_document_vente(
|
|||
|
||||
doc.Write()
|
||||
|
||||
# Process() sauf pour devis en brouillon
|
||||
if type_document != TypeDocumentVente.DEVIS:
|
||||
process.Process()
|
||||
logger.info("✓ Process() appelé")
|
||||
|
|
@ -147,7 +134,6 @@ def creer_document_vente(
|
|||
except Exception:
|
||||
logger.debug(" ↳ Process() ignoré pour devis brouillon")
|
||||
|
||||
# Commit transaction
|
||||
if transaction_active:
|
||||
try:
|
||||
self.cial.CptaApplication.CommitTrans()
|
||||
|
|
@ -157,7 +143,6 @@ def creer_document_vente(
|
|||
|
||||
time.sleep(2)
|
||||
|
||||
# ===== RÉCUPÉRATION DU NUMÉRO =====
|
||||
numero_document = _recuperer_numero_document(process, doc)
|
||||
|
||||
if not numero_document:
|
||||
|
|
@ -167,7 +152,6 @@ def creer_document_vente(
|
|||
|
||||
logger.info(f"📄 Numéro: {numero_document}")
|
||||
|
||||
# ===== RELECTURE POUR TOTAUX =====
|
||||
doc_final_data = _relire_document_final(
|
||||
self,
|
||||
config=config,
|
||||
|
|
@ -205,21 +189,17 @@ def _appliquer_remise_ligne(ligne_obj, remise_pourcent: float) -> bool:
|
|||
|
||||
dispatch = ligne_obj._oleobj_
|
||||
|
||||
# 1. Récupérer l'objet Remise
|
||||
dispid = dispatch.GetIDsOfNames(0, "Remise")
|
||||
remise_obj = dispatch.Invoke(dispid, 0, pythoncom.DISPATCH_PROPERTYGET, 1)
|
||||
remise_wrapper = win32com.client.Dispatch(remise_obj)
|
||||
|
||||
# 2. Définir la remise via FromString
|
||||
remise_wrapper.FromString(f"{remise_pourcent}%")
|
||||
|
||||
# 3. Calcul (optionnel mais recommandé)
|
||||
try:
|
||||
remise_wrapper.Calcul()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 4. Write la ligne
|
||||
ligne_obj.Write()
|
||||
|
||||
logger.info(f" Remise {remise_pourcent}% appliquée")
|
||||
|
|
@ -236,7 +216,6 @@ def _ajouter_ligne_document(
|
|||
"""VERSION FINALE AVEC REMISES FONCTIONNELLES"""
|
||||
logger.info(f" ├─ Ligne {idx}: {ligne_data['article_code']}")
|
||||
|
||||
# ===== CRÉATION LIGNE =====
|
||||
persist_article = factory_article.ReadReference(ligne_data["article_code"])
|
||||
if not persist_article:
|
||||
raise ValueError(f"Article {ligne_data['article_code']} introuvable")
|
||||
|
|
@ -255,7 +234,6 @@ def _ajouter_ligne_document(
|
|||
|
||||
quantite = float(ligne_data["quantite"])
|
||||
|
||||
# ===== ASSOCIATION ARTICLE =====
|
||||
try:
|
||||
ligne_obj.SetDefaultArticleReference(ligne_data["article_code"], quantite)
|
||||
except Exception:
|
||||
|
|
@ -265,7 +243,6 @@ def _ajouter_ligne_document(
|
|||
ligne_obj.DL_Design = designation_sage
|
||||
ligne_obj.DL_Qte = quantite
|
||||
|
||||
# ===== PRIX =====
|
||||
prix_auto = float(getattr(ligne_obj, "DL_PrixUnitaire", 0.0))
|
||||
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))
|
||||
logger.info(f" 💰 Prix: {prix_final}€")
|
||||
|
||||
# ===== WRITE INITIAL =====
|
||||
ligne_obj.Write()
|
||||
|
||||
# ===== APPLICATION REMISE (TOUTES LES LIGNES!) =====
|
||||
remise = ligne_data.get("remise_pourcentage", 0)
|
||||
if remise and remise > 0:
|
||||
logger.info(f" 🎯 Application remise {remise}%...")
|
||||
|
|
@ -364,7 +339,6 @@ def _relire_document_final(
|
|||
total_ttc = float(getattr(doc_final, "DO_TotalTTC", 0.0))
|
||||
reference_finale = getattr(doc_final, "DO_Ref", "")
|
||||
|
||||
# Récupérer le client depuis le document Sage
|
||||
try:
|
||||
client_obj = getattr(doc_final, "Client", None)
|
||||
if client_obj:
|
||||
|
|
@ -373,7 +347,6 @@ def _relire_document_final(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Date secondaire
|
||||
if config.champ_date_secondaire:
|
||||
try:
|
||||
date_livr = getattr(doc_final, "DO_DateLivr", None)
|
||||
|
|
@ -382,17 +355,14 @@ def _relire_document_final(
|
|||
except Exception:
|
||||
pass
|
||||
else:
|
||||
# Valeurs par défaut si relecture échoue
|
||||
total_ht = 0.0
|
||||
total_ttc = 0.0
|
||||
reference_finale = doc_data.get("reference", "")
|
||||
date_secondaire_value = doc_data.get(config.champ_date_secondaire)
|
||||
|
||||
# Fallback pour le code client (priorité: Sage > fallback > doc_data)
|
||||
if not client_code:
|
||||
client_code = client_code_fallback or doc_data.get("client", {}).get("code", "")
|
||||
|
||||
# Construction du résultat
|
||||
resultat = {
|
||||
config.champ_numero: numero_document,
|
||||
"total_ht": total_ht,
|
||||
|
|
@ -407,7 +377,6 @@ def _relire_document_final(
|
|||
"reference": reference_finale,
|
||||
}
|
||||
|
||||
# Ajout date secondaire si applicable
|
||||
if config.champ_date_secondaire:
|
||||
resultat[config.champ_date_secondaire] = date_secondaire_value
|
||||
|
||||
|
|
@ -447,7 +416,6 @@ def modifier_document_vente(
|
|||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||
doc.Read()
|
||||
|
||||
# Vérifications
|
||||
statut_actuel = getattr(doc, "DO_Statut", 0)
|
||||
|
||||
if statut_actuel == 5:
|
||||
|
|
@ -506,7 +474,6 @@ def modifier_document_vente(
|
|||
logger.info(f" Référence: {modif_ref}")
|
||||
logger.info(f" Lignes: {modif_lignes}")
|
||||
|
||||
# Reporter référence et statut après les lignes
|
||||
doc_data_temp = doc_data.copy()
|
||||
reference_a_modifier = None
|
||||
statut_a_modifier = None
|
||||
|
|
@ -567,7 +534,6 @@ def modifier_document_vente(
|
|||
elif modif_lignes:
|
||||
logger.info("🔄 REMPLACEMENT COMPLET DES LIGNES...")
|
||||
|
||||
# Dates
|
||||
if modif_date:
|
||||
doc.DO_Date = pywintypes.Time(
|
||||
normaliser_date(doc_data_temp.get(config.champ_date_principale))
|
||||
|
|
@ -580,7 +546,6 @@ def modifier_document_vente(
|
|||
)
|
||||
champs_modifies.append(config.champ_date_secondaire)
|
||||
|
||||
# 🔥 CONFIGURATION SPÉCIFIQUE FACTURE (avant lignes)
|
||||
if type_document == TypeDocumentVente.FACTURE:
|
||||
_configurer_facture(self, doc)
|
||||
|
||||
|
|
@ -596,7 +561,6 @@ def modifier_document_vente(
|
|||
|
||||
factory_article = self.cial.FactoryArticle
|
||||
|
||||
# Suppression lignes existantes
|
||||
if nb_lignes_initial > 0:
|
||||
logger.info(f" 🗑️ Suppression {nb_lignes_initial} lignes...")
|
||||
for idx in range(nb_lignes_initial, 0, -1):
|
||||
|
|
@ -612,10 +576,8 @@ def modifier_document_vente(
|
|||
logger.warning(f" Ligne {idx}: {e}")
|
||||
logger.info(" ✓ Lignes supprimées")
|
||||
|
||||
# Ajout nouvelles lignes avec REMISES
|
||||
logger.info(f" ➕ Ajout {nb_nouvelles} lignes...")
|
||||
for idx, ligne_data in enumerate(nouvelles_lignes, 1):
|
||||
# 🔥 UTILISE _ajouter_ligne_document qui applique les remises
|
||||
_ajouter_ligne_document(
|
||||
cial=self.cial,
|
||||
factory_lignes=factory_lignes,
|
||||
|
|
|
|||
|
|
@ -167,10 +167,8 @@ def _parser_heure_sage(do_heure) -> str:
|
|||
return "00:00:00"
|
||||
|
||||
try:
|
||||
# Convertir en entier pour éliminer les zéros de padding SQL
|
||||
heure_int = int(str(do_heure).strip())
|
||||
|
||||
# Formatter en string 6 caractères (HHMMSS)
|
||||
heure_str = str(heure_int).zfill(6)
|
||||
|
||||
hh = int(heure_str[0:2])
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ def contact_to_dict(row) -> Dict:
|
|||
|
||||
def _collaborators_to_dict(row) -> Optional[dict]:
|
||||
"""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:
|
||||
return None
|
||||
|
||||
|
|
@ -157,7 +156,6 @@ def collaborators_to_dict(row):
|
|||
def tiers_to_dict(row) -> dict:
|
||||
"""Convertit une ligne SQL en dictionnaire tiers"""
|
||||
tiers = {
|
||||
# IDENTIFICATION
|
||||
"numero": _safe_strip(row.CT_Num),
|
||||
"intitule": _safe_strip(row.CT_Intitule),
|
||||
"type_tiers": row.CT_Type,
|
||||
|
|
@ -167,7 +165,6 @@ def tiers_to_dict(row) -> dict:
|
|||
"siret": _safe_strip(row.CT_Siret),
|
||||
"tva_intra": _safe_strip(row.CT_Identifiant),
|
||||
"code_naf": _safe_strip(row.CT_Ape),
|
||||
# ADRESSE
|
||||
"contact": _safe_strip(row.CT_Contact),
|
||||
"adresse": _safe_strip(row.CT_Adresse),
|
||||
"complement": _safe_strip(row.CT_Complement),
|
||||
|
|
@ -175,19 +172,16 @@ def tiers_to_dict(row) -> dict:
|
|||
"ville": _safe_strip(row.CT_Ville),
|
||||
"region": _safe_strip(row.CT_CodeRegion),
|
||||
"pays": _safe_strip(row.CT_Pays),
|
||||
# TELECOM
|
||||
"telephone": _safe_strip(row.CT_Telephone),
|
||||
"telecopie": _safe_strip(row.CT_Telecopie),
|
||||
"email": _safe_strip(row.CT_EMail),
|
||||
"site_web": _safe_strip(row.CT_Site),
|
||||
"facebook": _safe_strip(row.CT_Facebook),
|
||||
"linkedin": _safe_strip(row.CT_LinkedIn),
|
||||
# TAUX
|
||||
"taux01": row.CT_Taux01,
|
||||
"taux02": row.CT_Taux02,
|
||||
"taux03": row.CT_Taux03,
|
||||
"taux04": row.CT_Taux04,
|
||||
# STATISTIQUES
|
||||
"statistique01": _safe_strip(row.CT_Statistique01),
|
||||
"statistique02": _safe_strip(row.CT_Statistique02),
|
||||
"statistique03": _safe_strip(row.CT_Statistique03),
|
||||
|
|
@ -198,13 +192,11 @@ def tiers_to_dict(row) -> dict:
|
|||
"statistique08": _safe_strip(row.CT_Statistique08),
|
||||
"statistique09": _safe_strip(row.CT_Statistique09),
|
||||
"statistique10": _safe_strip(row.CT_Statistique10),
|
||||
# COMMERCIAL
|
||||
"encours_autorise": row.CT_Encours,
|
||||
"assurance_credit": row.CT_Assurance,
|
||||
"langue": row.CT_Langue,
|
||||
"commercial_code": row.CO_No,
|
||||
"commercial": _collaborators_to_dict(row),
|
||||
# FACTURATION
|
||||
"lettrage_auto": (row.CT_Lettrage == 1),
|
||||
"est_actif": (row.CT_Sommeil == 0),
|
||||
"type_facture": row.CT_Facture,
|
||||
|
|
@ -216,16 +208,12 @@ def tiers_to_dict(row) -> dict:
|
|||
"exclure_relance": (row.CT_NotRappel == 1),
|
||||
"exclure_penalites": (row.CT_NotPenal == 1),
|
||||
"bon_a_payer": row.CT_BonAPayer,
|
||||
# LOGISTIQUE
|
||||
"priorite_livraison": row.CT_PrioriteLivr,
|
||||
"livraison_partielle": row.CT_LivrPartielle,
|
||||
"delai_transport": row.CT_DelaiTransport,
|
||||
"delai_appro": row.CT_DelaiAppro,
|
||||
# COMMENTAIRE
|
||||
"commentaire": _safe_strip(row.CT_Commentaire),
|
||||
# ANALYTIQUE
|
||||
"section_analytique": _safe_strip(row.CA_Num),
|
||||
# ORGANISATION / SURVEILLANCE
|
||||
"mode_reglement_code": row.MR_No,
|
||||
"surveillance_active": (row.CT_Surveillance == 1),
|
||||
"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_chiffre_affaires": row.CT_SvCA,
|
||||
"sv_resultat": row.CT_SvResultat,
|
||||
# COMPTE GENERAL ET CATEGORIES
|
||||
"compte_general": _safe_strip(row.CG_NumPrinc),
|
||||
"categorie_tarif": row.N_CatTarif,
|
||||
"categorie_compta": row.N_CatCompta,
|
||||
|
|
|
|||
Loading…
Reference in a new issue