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:
parent
0763a56b06
commit
8b676f7195
2 changed files with 352 additions and 77 deletions
179
main.py
179
main.py
|
|
@ -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
|
||||
# =====================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
# ========================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue