feat: Enhance mandatory invoice field population by adding fallback logic for payment, journal, and numbering, and including tax type/code.

This commit is contained in:
Fanilo-Nantenaina 2025-11-28 11:33:19 +03:00
parent 0763a56b06
commit 8b676f7195
2 changed files with 352 additions and 77 deletions

179
main.py
View file

@ -1513,6 +1513,185 @@ def diagnostiquer_exigences_facture():
raise HTTPException(500, str(e))
@app.post("/sage/test/creer-facture-vide", dependencies=[Depends(verify_token)])
def tester_creation_facture_vide():
"""
🧪 TEST: Crée une facture vide pour identifier les champs obligatoires
Ce test permet de découvrir EXACTEMENT quels champs Sage exige
"""
try:
if not sage or not sage.cial:
raise HTTPException(503, "Service Sage indisponible")
with sage._com_context(), sage._lock_com:
logger.info("[TEST] Creation facture test...")
# 1. Créer le process
process = sage.cial.CreateProcess_Document(60)
doc = process.Document
try:
doc = win32com.client.CastTo(doc, "IBODocumentVente3")
except:
pass
# 2. Définir UNIQUEMENT les champs absolument critiques
import pywintypes
# Date (obligatoire)
doc.DO_Date = pywintypes.Time(datetime.now())
# Client (obligatoire) - Utiliser le premier client disponible
factory_client = sage.cial.CptaApplication.FactoryClient
persist_client = factory_client.List(1)
if persist_client:
client = sage._cast_client(persist_client)
if client:
doc.SetDefaultClient(client)
client_code = getattr(client, "CT_Num", "?")
logger.info(f"[TEST] Client test: {client_code}")
# 3. Écrire sans Process() pour voir les valeurs par défaut
doc.Write()
doc.Read()
# 4. Analyser tous les champs
champs_analyse = {}
for attr in dir(doc):
if attr.startswith("DO_") or attr.startswith("CT_"):
try:
valeur = getattr(doc, attr, None)
if valeur is not None:
champs_analyse[attr] = {
"valeur": str(valeur),
"type": type(valeur).__name__,
}
except:
pass
logger.info(f"[TEST] {len(champs_analyse)} champs analyses")
# 5. Tester Process() pour voir l'erreur exacte
erreur_process = None
try:
process.Process()
logger.info("[TEST] Process() reussi (inattendu!)")
except Exception as e:
erreur_process = str(e)
logger.info(f"[TEST] Process() echoue comme prevu: {e}")
# Ne pas commit - c'est juste un test
try:
sage.cial.CptaApplication.RollbackTrans()
except:
pass
return {
"success": True,
"champs_definis": champs_analyse,
"erreur_process": erreur_process,
"conseil": "Les champs manquants dans l'erreur sont probablement obligatoires",
}
except Exception as e:
logger.error(f"[TEST] Erreur: {e}", exc_info=True)
raise HTTPException(500, str(e))
@app.get("/sage/config/parametres-facture", dependencies=[Depends(verify_token)])
def verifier_parametres_facture():
"""
🔍 Vérifie les paramètres Sage pour la création de factures
"""
try:
if not sage or not sage.cial:
raise HTTPException(503, "Service Sage indisponible")
with sage._com_context(), sage._lock_com:
parametres = {}
# Paramètres société
try:
param_societe = sage.cial.CptaApplication.ParametreSociete
parametres["societe"] = {
"journal_vente_defaut": getattr(
param_societe, "P_CodeJournalVte", "N/A"
),
"mode_reglement_defaut": getattr(
param_societe, "P_ModeRegl", "N/A"
),
"souche_facture": getattr(param_societe, "P_SoucheFacture", "N/A"),
}
except Exception as e:
parametres["erreur_societe"] = str(e)
# Tester un client existant
try:
factory_client = sage.cial.CptaApplication.FactoryClient
persist = factory_client.List(1)
if persist:
client = sage._cast_client(persist)
if client:
parametres["exemple_client"] = {
"code": getattr(client, "CT_Num", "?"),
"mode_reglement": getattr(client, "CT_ModeRegl", "N/A"),
"conditions_reglement": getattr(
client, "CT_CondRegl", "N/A"
),
}
except Exception as e:
parametres["erreur_client"] = str(e)
# Journaux disponibles
try:
factory_journal = sage.cial.CptaApplication.FactoryJournal
journaux = []
index = 1
while index <= 20: # Max 20 journaux
try:
persist_journal = factory_journal.List(index)
if persist_journal is None:
break
# Cast en journal
journal = win32com.client.CastTo(persist_journal, "IBOJournal3")
journal.Read()
journaux.append(
{
"code": getattr(journal, "JO_Num", "?"),
"intitule": getattr(journal, "JO_Intitule", "?"),
"type": getattr(journal, "JO_Type", "?"),
}
)
index += 1
except:
index += 1
break
parametres["journaux_disponibles"] = journaux
except Exception as e:
parametres["erreur_journaux"] = str(e)
return {
"success": True,
"parametres": parametres,
"conseil": "Utilisez ces valeurs pour remplir les champs obligatoires des factures",
}
except Exception as e:
logger.error(f"Erreur verification config: {e}", exc_info=True)
raise HTTPException(500, str(e))
# =====================================================
# LANCEMENT
# =====================================================

View file

@ -1355,104 +1355,200 @@ class SageConnector:
# ÉTAPE 8 : COMPLÉTER LES CHAMPS OBLIGATOIRES POUR FACTURE
# ========================================
if type_cible == 60: # Facture
logger.info(
"[TRANSFORM] Completion champs obligatoires facture..."
)
# Mode de règlement (obligatoire pour facture)
logger.info("[TRANSFORM] Completion champs obligatoires facture...")
# 1. Mode de règlement (CRITIQUE)
try:
# Récupérer le mode de règlement du client
mode_reglement = None
# Essayer de récupérer du client
if client_obj_cible:
mode_reglement = getattr(
client_obj_cible, "CT_ModeRegl", None
)
if mode_reglement:
doc_cible.DO_ModeRegl = mode_reglement
logger.info(
f"[TRANSFORM] Mode reglement: {mode_reglement}"
)
except Exception as e:
logger.warning(
f"[TRANSFORM] Impossible de definir mode reglement: {e}"
)
# Forcer un mode par défaut (0 = Aucun)
try:
doc_cible.DO_ModeRegl = 0
except:
pass
# Conditions de règlement
try:
if client_obj_cible:
cond_reglement = getattr(
client_obj_cible, "CT_CondRegl", None
)
if cond_reglement:
doc_cible.DO_CondRegl = cond_reglement
except Exception as e:
logger.warning(
f"[TRANSFORM] Impossible de definir conditions reglement: {e}"
)
# Code journal (critique pour facture)
try:
# Vérifier si un code journal est déjà défini
journal_actuel = getattr(doc_cible, "DO_CodeJournal", None)
if not journal_actuel:
# Essayer de récupérer le journal par défaut
try:
# Généralement "VE" pour ventes
doc_cible.DO_CodeJournal = "VE"
logger.info("[TRANSFORM] Code journal: VE")
mode_reglement = getattr(client_obj_cible, 'CT_ModeRegl', None)
if mode_reglement is not None:
logger.info(f"[TRANSFORM] Mode reglement client: {mode_reglement}")
except:
pass
# Si pas trouvé, utiliser celui du document source
if mode_reglement is None:
try:
mode_reglement = getattr(doc_source, 'DO_ModeRegl', None)
if mode_reglement is not None:
logger.info(f"[TRANSFORM] Mode reglement source: {mode_reglement}")
except:
pass
# Forcer une valeur par défaut si toujours None
if mode_reglement is None:
mode_reglement = 0 # 0 = Aucun
logger.info("[TRANSFORM] Mode reglement defaut: 0 (Aucun)")
doc_cible.DO_ModeRegl = mode_reglement
except Exception as e:
logger.warning(
f"[TRANSFORM] Impossible de definir code journal: {e}"
)
# Souche de numérotation
logger.error(f"[TRANSFORM] Erreur mode reglement: {e}")
raise
# 2. Conditions de règlement
try:
souche = getattr(doc_cible, "DO_Souche", None)
if not souche or souche == 0:
# Utiliser souche par défaut (généralement 0)
doc_cible.DO_Souche = 0
cond_reglement = None
if client_obj_cible:
try:
cond_reglement = getattr(client_obj_cible, 'CT_CondRegl', None)
if cond_reglement is not None:
doc_cible.DO_CondRegl = cond_reglement
logger.info(f"[TRANSFORM] Conditions reglement: {cond_reglement}")
except:
pass
# Fallback sur document source
if cond_reglement is None:
try:
cond_reglement = getattr(doc_source, 'DO_CondRegl', None)
if cond_reglement is not None:
doc_cible.DO_CondRegl = cond_reglement
except:
pass
except Exception as e:
logger.warning(
f"[TRANSFORM] Impossible de definir souche: {e}"
)
logger.warning(f"[TRANSFORM] Conditions reglement non definies: {e}")
# 3. Code journal (CRITIQUE pour comptabilisation)
try:
journal = None
# Essayer de récupérer du document source
try:
journal = getattr(doc_source, 'DO_CodeJournal', None)
if journal:
logger.info(f"[TRANSFORM] Journal source: {journal}")
except:
pass
# Si pas trouvé, essayer le journal par défaut de Sage
if not journal:
try:
# Récupérer le paramètre société
param_societe = self.cial.CptaApplication.ParametreSociete
if param_societe:
# Journal vente par défaut
journal = getattr(param_societe, 'P_CodeJournalVte', 'VE')
logger.info(f"[TRANSFORM] Journal societe: {journal}")
except:
pass
# Dernier recours : "VE" (standard Sage)
if not journal:
journal = "VE"
logger.info("[TRANSFORM] Journal par defaut: VE")
doc_cible.DO_CodeJournal = journal
except Exception as e:
logger.error(f"[TRANSFORM] Erreur code journal: {e}")
raise
# 4. Souche de numérotation
try:
souche = getattr(doc_source, 'DO_Souche', 0)
doc_cible.DO_Souche = souche
logger.info(f"[TRANSFORM] Souche: {souche}")
except Exception as e:
logger.warning(f"[TRANSFORM] Erreur souche: {e}")
try:
doc_cible.DO_Souche = 0
except:
pass
# 5. Type de calcul (taxes)
try:
type_calcul = getattr(doc_source, 'DO_TypeCalcul', None)
if type_calcul is not None:
doc_cible.DO_TypeCalcul = type_calcul
logger.info(f"[TRANSFORM] Type calcul: {type_calcul}")
except Exception as e:
logger.debug(f"[TRANSFORM] Type calcul non defini: {e}")
# 6. Code taxe (important si TVA)
try:
code_taxe = getattr(doc_source, 'DO_CodeTaxe1', None)
if code_taxe is not None:
doc_cible.DO_CodeTaxe1 = code_taxe
logger.info(f"[TRANSFORM] Code taxe: {code_taxe}")
except Exception as e:
logger.debug(f"[TRANSFORM] Code taxe non defini: {e}")
# 7. Écrire le document avec tous les champs
logger.info("[TRANSFORM] Ecriture document avec champs obligatoires...")
doc_cible.Write()
# ==============================================================================
# PARTIE 3 : AMÉLIORATION ÉTAPE 9 - Validation avec diagnostic détaillé
# ==============================================================================
# ========================================
# ÉTAPE 9 : VALIDER LE DOCUMENT
# ========================================
logger.info("[TRANSFORM] Validation document cible...")
doc_cible.Write()
# Relire pour vérifier que tout est OK
doc_cible.Read()
# Diagnostic pré-validation
logger.info("[TRANSFORM] === PRE-VALIDATION CHECK ===")
champs_critiques = {
"Type": getattr(doc_cible, 'DO_Type', '?'),
"Client": getattr(doc_cible, 'CT_Num', '?'),
"Date": getattr(doc_cible, 'DO_Date', '?'),
"Mode reglement": getattr(doc_cible, 'DO_ModeRegl', '?'),
"Code journal": getattr(doc_cible, 'DO_CodeJournal', '?'),
"Souche": getattr(doc_cible, 'DO_Souche', '?'),
"Statut": getattr(doc_cible, 'DO_Statut', '?'),
}
for nom, valeur in champs_critiques.items():
logger.info(f" {nom}: {valeur}")
# Vérifier que les champs critiques ne sont pas vides
champs_manquants = []
if not getattr(doc_cible, 'CT_Num', None):
champs_manquants.append("Client (CT_Num)")
if type_cible == 60: # Facture
if not getattr(doc_cible, 'DO_CodeJournal', None):
champs_manquants.append("Code journal (DO_CodeJournal)")
if getattr(doc_cible, 'DO_ModeRegl', None) is None:
champs_manquants.append("Mode reglement (DO_ModeRegl)")
if champs_manquants:
erreur = f"Champs obligatoires manquants: {', '.join(champs_manquants)}"
logger.error(f"[TRANSFORM] {erreur}")
raise ValueError(erreur)
# Lancer le processus
try:
logger.info("[TRANSFORM] Appel Process()...")
process.Process()
logger.info("[TRANSFORM] Document cible valide")
logger.info("[TRANSFORM] Document cible valide avec succes")
except Exception as e:
logger.error(f"[TRANSFORM] Erreur Process(): {e}")
# Diagnostic détaillé
logger.error(f"[TRANSFORM] ERREUR Process(): {e}")
logger.error("[TRANSFORM] === DIAGNOSTIC COMPLET ===")
# Afficher TOUS les attributs du document
try:
logger.info("[TRANSFORM] === DIAGNOSTIC DOCUMENT ===")
logger.info(f"Type: {getattr(doc_cible, 'DO_Type', '?')}")
logger.info(f"Client: {getattr(doc_cible, 'CT_Num', '?')}")
logger.info(f"Date: {getattr(doc_cible, 'DO_Date', '?')}")
logger.info(
f"Mode reglement: {getattr(doc_cible, 'DO_ModeRegl', '?')}"
)
logger.info(
f"Code journal: {getattr(doc_cible, 'DO_CodeJournal', '?')}"
)
logger.info(
f"Souche: {getattr(doc_cible, 'DO_Souche', '?')}"
)
for attr in dir(doc_cible):
if attr.startswith('DO_') or attr.startswith('CT_'):
try:
valeur = getattr(doc_cible, attr, 'N/A')
logger.error(f" {attr}: {valeur}")
except:
pass
except:
pass
raise
# ========================================