validation facture et règlement effectuées avec succès, mais approche dangereuse !

This commit is contained in:
fanilo 2026-01-15 15:22:28 +01:00
parent 7dc5d03c4c
commit f5c5a87d0d
4 changed files with 679 additions and 70 deletions

101
main.py
View file

@ -1457,6 +1457,11 @@ def regler_facture_endpoint(
date_reglement=date_reg,
reference=req.reference or "",
libelle=req.libelle or "",
code_journal=req.code_journal, # None = auto
devise_code=req.devise_code,
cours_devise=req.cours_devise,
tva_encaissement=req.tva_encaissement,
compte_general=req.compte_general,
)
return {
"success": True,
@ -1474,15 +1479,23 @@ def regler_facture_endpoint(
@app.post("/sage/reglements/multiple", dependencies=[Depends(verify_token)])
def regler_factures_client_endpoint(req: ReglementMultipleRequest):
try:
date_reg = (
datetime.combine(req.date_reglement, datetime.min.time())
if req.date_reglement
else None
)
resultat = sage.regler_factures_client(
client_code=req.client_code,
montant_total=req.montant_total,
mode_reglement=req.mode_reglement,
date_reglement=req.date_reglement,
date_reglement=date_reg,
reference=req.reference or "",
libelle=req.libelle or "",
code_journal=req.code_journal,
code_journal=req.code_journal, # None = auto
numeros_factures=req.numeros_factures,
devise_code=req.devise_code,
cours_devise=req.cours_devise,
tva_encaissement=req.tva_encaissement,
)
return {"success": True, "data": resultat}
@ -1591,21 +1604,75 @@ def get_statut_validation_endpoint(numero_facture: str):
@app.get("/sage/reglements/modes", dependencies=[Depends(verify_token)])
def get_modes_reglement():
return {
"success": True,
"data": {
"modes": [
{"code": 1, "libelle": "Virement"},
{"code": 2, "libelle": "Chèque"},
{"code": 3, "libelle": "Traite"},
{"code": 4, "libelle": "Carte bancaire"},
{"code": 5, "libelle": "LCR"},
{"code": 6, "libelle": "Prélèvement"},
{"code": 7, "libelle": "Espèces"},
]
},
}
def get_modes_reglement_endpoint():
"""Récupère les modes de règlement depuis Sage"""
try:
modes = sage.lire_modes_reglement()
return {"success": True, "data": {"modes": modes}}
except Exception as e:
logger.error(f"Erreur lecture modes règlement: {e}")
raise HTTPException(500, str(e))
@app.get("/sage/devises", dependencies=[Depends(verify_token)])
def get_devises_endpoint():
"""Récupère les devises disponibles depuis Sage"""
try:
devises = sage.lire_devises()
return {"success": True, "data": {"devises": devises}}
except Exception as e:
logger.error(f"Erreur lecture devises: {e}")
raise HTTPException(500, str(e))
@app.get("/sage/journaux/tresorerie", dependencies=[Depends(verify_token)])
def get_journaux_tresorerie_endpoint():
"""Récupère les journaux de trésorerie (banque + caisse)"""
try:
journaux = sage.lire_journaux_tresorerie()
return {"success": True, "data": {"journaux": journaux}}
except Exception as e:
logger.error(f"Erreur lecture journaux trésorerie: {e}")
raise HTTPException(500, str(e))
@app.get("/sage/comptes-generaux", dependencies=[Depends(verify_token)])
def get_comptes_generaux_endpoint(
prefixe: Optional[str] = Query(None, description="Filtre par préfixe (ex: 41, 51)"),
type_compte: Optional[str] = Query(
None,
description="Type: client, fournisseur, banque, caisse, tva, produit, charge",
),
):
"""Récupère les comptes généraux"""
try:
comptes = sage.lire_comptes_generaux(prefixe=prefixe, type_compte=type_compte)
return {"success": True, "data": {"comptes": comptes, "total": len(comptes)}}
except Exception as e:
logger.error(f"Erreur lecture comptes généraux: {e}")
raise HTTPException(500, str(e))
@app.get("/sage/tva/taux", dependencies=[Depends(verify_token)])
def get_tva_taux_endpoint():
"""Récupère les taux de TVA"""
try:
taux = sage.lire_tva_taux()
return {"success": True, "data": {"taux": taux}}
except Exception as e:
logger.error(f"Erreur lecture taux TVA: {e}")
raise HTTPException(500, str(e))
@app.get("/sage/parametres/encaissement", dependencies=[Depends(verify_token)])
def get_parametres_encaissement_endpoint():
"""Récupère les paramètres TVA sur encaissement"""
try:
params = sage.lire_parametres_encaissement()
return {"success": True, "data": params}
except Exception as e:
logger.error(f"Erreur lecture paramètres encaissement: {e}")
raise HTTPException(500, str(e))
@app.get("/sage/journaux")

View file

@ -94,6 +94,13 @@ from utils.functions.society.societe_data import (
)
from utils.documents.settle import (
lire_modes_reglement,
lire_devises,
lire_journaux_tresorerie,
lire_comptes_generaux,
lire_tva_taux,
lire_parametres_encaissement,
_get_modes_reglement_standards,
regler_facture as _regler_facture,
regler_factures_client as _regler_factures_client,
lire_reglements_client as _lire_reglements_client,
@ -8017,33 +8024,47 @@ class SageConnector:
def regler_facture(
self,
numero_facture,
montant,
mode_reglement=2,
date_reglement=None,
reference="",
libelle="",
numero_facture: str,
montant: float,
mode_reglement: int = 0,
date_reglement: datetime = None,
reference: str = "",
libelle: str = "",
code_journal: str = None,
devise_code: int = 0,
cours_devise: float = 1.0,
tva_encaissement: bool = False,
compte_general: str = None,
):
"""Règle une facture"""
return _regler_facture(
self,
numero_facture,
montant,
mode_reglement,
date_reglement,
reference,
libelle,
numero_facture=numero_facture,
montant=montant,
mode_reglement=mode_reglement,
date_reglement=date_reglement,
reference=reference,
libelle=libelle,
code_journal=code_journal,
devise_code=devise_code,
cours_devise=cours_devise,
tva_encaissement=tva_encaissement,
compte_general=compte_general,
)
def regler_factures_client(
self,
client_code: str,
montant_total: float,
mode_reglement: int = 2,
mode_reglement: int = 0,
date_reglement: datetime = None,
reference: str = "",
libelle: str = "",
code_journal: str = "BEU",
code_journal: str = None,
numeros_factures: List[str] = None,
devise_code: int = 0,
cours_devise: float = 1.0,
tva_encaissement: bool = False,
):
"""Règle plusieurs factures d'un client"""
return _regler_factures_client(
@ -8056,6 +8077,9 @@ class SageConnector:
libelle=libelle,
code_journal=code_journal,
numeros_factures=numeros_factures,
devise_code=devise_code,
cours_devise=cours_devise,
tva_encaissement=tva_encaissement,
)
def lire_reglements_facture(self, numero_facture: str):
@ -8102,3 +8126,21 @@ class SageConnector:
def introspecter_document_complet(self, numero_facture: str) -> dict:
return _introspect_doc(self, numero_facture)
def lire_modes_reglement(self) -> dict:
return lire_modes_reglement(self)
def lire_devises(self) -> dict:
return lire_devises(self)
def lire_journaux_tresorerie(self) -> dict:
return lire_journaux_tresorerie(self)
def lire_comptes_generaux(self, prefixe, type_compte) -> dict:
return lire_comptes_generaux(self, prefixe, type_compte)
def lire_tva_taux(self) -> dict:
return lire_tva_taux(self)
def lire_parametres_encaissement(self) -> dict:
return lire_parametres_encaissement(self)

View file

@ -69,6 +69,10 @@ class ReglementFactureRequest(BaseModel):
code_journal: str = Field(
default="BEU", max_length=6, description="Code journal comptable"
)
devise_code: Optional[int] = Field(0)
cours_devise: Optional[float] = Field(1.0)
tva_encaissement: Optional[bool] = Field(False)
compte_general: Optional[str] = Field(None)
@field_validator("montant")
def validate_montant(cls, v):
@ -103,6 +107,9 @@ class ReglementMultipleRequest(BaseModel):
default=None,
description="Liste des factures à régler (sinon: plus anciennes d'abord)",
)
devise_code: Optional[int] = Field(0)
cours_devise: Optional[float] = Field(1.0)
tva_encaissement: Optional[bool] = Field(False)
@field_validator("client_code", mode="before")
def strip_client_code(cls, v):

View file

@ -12,24 +12,112 @@ logger = logging.getLogger(__name__)
def _get_journal_auto(self, mode_reglement: int) -> str:
with self._get_sql_connection() as conn:
cursor = conn.cursor()
if mode_reglement == ModeReglement.ESPECES:
# Mode Espèces = 2 (selon l'image Sage fournie)
if mode_reglement == 2: # Espèces
cursor.execute("""
SELECT TOP 1 JO_Num
FROM F_JOURNAUX
WHERE JO_Type = 2
AND JO_Reglement = 1
AND CG_Num LIKE '53%'
AND (JO_Sommeil = 0 OR JO_Sommeil IS NULL)
ORDER BY JO_Num
""")
else:
# Autres modes → Banque
cursor.execute("""
SELECT TOP 1 JO_Num
FROM F_JOURNAUX
WHERE JO_Type = 2
AND JO_Reglement = 1
AND CG_Num LIKE '51%'
AND (JO_Sommeil = 0 OR JO_Sommeil IS NULL)
ORDER BY JO_Num
""")
row = cursor.fetchone()
if row:
return row[0].strip()
# Fallback: premier journal de trésorerie disponible
cursor.execute("""
SELECT TOP 1 JO_Num
FROM F_JOURNAUX
WHERE JO_Type = 2
AND JO_Reglement = 1
AND (JO_Sommeil = 0 OR JO_Sommeil IS NULL)
ORDER BY JO_Num
""")
row = cursor.fetchone()
if row:
return row[0].strip()
raise ValueError("Aucun journal de trésorerie configuré")
def _valider_coherence_journal_mode(self, code_journal: str, mode_reglement: int):
with self._get_sql_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT TOP 1 JO_Num FROM F_JOURNAUX WHERE JO_Type = 2 AND JO_Reglement = 1 AND CG_Num LIKE '53%' ORDER BY JO_Num"
"""
SELECT CG_Num
FROM F_JOURNAUX
WHERE JO_Num = ?
""",
(code_journal,),
)
row = cursor.fetchone()
if not row:
raise ValueError(f"Journal {code_journal} introuvable")
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:
cursor.execute(
"SELECT TOP 1 JO_Num FROM F_JOURNAUX WHERE JO_Type = 2 AND JO_Reglement = 1 AND CG_Num LIKE '51%' ORDER BY JO_Num"
# 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})"
)
def _get_mode_reglement_libelle(self, mode_reglement: int) -> str:
"""Récupère le libellé d'un mode de règlement"""
with self._get_sql_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT R_Intitule
FROM P_REGLEMENT
WHERE cbIndice = ?
""",
(mode_reglement,),
)
row = cursor.fetchone()
if row:
return row[0].strip()
cursor.execute(
"SELECT TOP 1 JO_Num FROM F_JOURNAUX WHERE JO_Type = 2 AND JO_Reglement = 1 ORDER BY JO_Num"
)
row = cursor.fetchone()
if row:
return row[0].strip()
raise ValueError("Aucun journal de trésorerie configuré")
return (row[0] or "").strip()
# Fallback sur les libellés standards
libelles = {
0: "Chèque",
1: "Virement",
2: "Espèces",
3: "LCR Acceptée",
4: "LCR non acceptée",
5: "BOR",
6: "Prélèvement",
7: "Carte bancaire",
8: "Bon d'achat",
}
return libelles.get(mode_reglement, f"Mode {mode_reglement}")
def lire_journaux_banque(self) -> List[Dict]:
@ -83,10 +171,15 @@ def regler_facture(
self,
numero_facture: str,
montant: float,
mode_reglement: int = ModeReglement.CHEQUE,
mode_reglement: int = 0, # 0 = Chèque par défaut
date_reglement: datetime = None,
reference: str = "",
libelle: str = "",
code_journal: str = None, # Si None, déduit automatiquement
devise_code: int = 0,
cours_devise: float = 1.0,
tva_encaissement: bool = False,
compte_general: str = None,
) -> Dict:
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
@ -94,9 +187,17 @@ def regler_facture(
raise ValueError("Le montant du règlement doit être positif")
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(
f"Règlement facture {numero_facture}: {montant}€ (mode: {mode_reglement}, journal: {code_journal})"
f"Règlement facture {numero_facture}: {montant}"
f"(mode: {mode_reglement}, journal: {code_journal}, devise: {devise_code})"
)
try:
@ -122,6 +223,7 @@ 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)
@ -131,22 +233,28 @@ 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,
echeance,
montant,
mode_reglement,
date_reglement,
reference,
libelle,
code_journal,
client_code,
numero_facture,
doc=doc,
echeance=echeance,
montant=montant,
mode_reglement=mode_reglement,
date_reglement=date_reglement,
reference=reference,
libelle=libelle,
code_journal=code_journal,
client_code=client_code,
numero_facture=numero_facture,
devise_code=devise_code,
cours_devise=cours_devise,
tva_encaissement=tva_encaissement,
compte_general=compte_general,
)
time.sleep(0.3)
@ -161,16 +269,22 @@ 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 {
"numero_facture": numero_facture,
"numero_reglement": numero_reglement,
"montant_regle": montant,
"date_reglement": date_reglement.strftime("%Y-%m-%d"),
"mode_reglement": mode_reglement,
"mode_reglement_libelle": ModeReglement.get_libelle(mode_reglement),
"mode_reglement_libelle": mode_libelle,
"reference": reference,
"libelle": libelle,
"code_journal": code_journal,
"devise_code": devise_code,
"cours_devise": cours_devise,
"tva_encaissement": tva_encaissement,
"total_facture": total_ttc,
"solde_restant": nouveau_solde,
"facture_soldee": nouveau_solde < 0.01,
@ -217,6 +331,10 @@ def _executer_reglement_com(
code_journal,
client_code,
numero_facture,
devise_code=0,
cours_devise=1.0,
tva_encaissement=False,
compte_general=None,
):
erreurs = []
@ -266,7 +384,6 @@ def _executer_reglement_com(
# 5. Mode de règlement via l'objet Reglement
try:
# Lire le mode de règlement depuis la base
mode_factory = getattr(
self.cial.CptaApplication, "FactoryModeReglement", None
)
@ -278,15 +395,59 @@ 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
logger.info(f" RG_Devise: {devise_code}")
except Exception as e:
logger.debug(f" RG_Devise: {e}")
try:
reg.RG_Cours = cours_devise
logger.info(f" RG_Cours: {cours_devise}")
except Exception as e:
logger.debug(f" RG_Cours: {e}")
# Montant en devise
try:
montant_devise = montant * cours_devise
reg.RG_MontantDev = montant_devise
logger.info(f" RG_MontantDev: {montant_devise}")
except Exception as e:
logger.debug(f" RG_MontantDev: {e}")
# 7. TVA sur encaissement
if tva_encaissement:
try:
reg.RG_Encaissement = 1
logger.info(" RG_Encaissement: 1 (TVA sur encaissement)")
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
cg_persist = cg_factory.ReadNumero(compte_general)
if cg_persist:
reg.CompteG = cg_persist
logger.info(f" CompteG défini: {compte_general}")
except Exception as e:
logger.debug(f" CompteG: {e}")
# 9. Référence et libellé
if reference:
try:
reg.RG_Reference = reference
logger.info(f" RG_Reference: {reference}")
except Exception:
pass
if libelle:
try:
reg.RG_Libelle = libelle
logger.info(f" RG_Libelle: {libelle}")
except Exception:
pass
@ -300,12 +461,12 @@ def _executer_reglement_com(
except Exception:
pass
# 6. ÉCRIRE le règlement
# 10. ÉCRIRE le règlement
reg.Write()
numero = getattr(reg, "RG_Piece", None)
logger.info(f" Règlement écrit avec numéro: {numero}")
# 7. Créer le lien règlement-échéance via la factory DU RÈGLEMENT
# 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)
@ -325,7 +486,7 @@ def _executer_reglement_com(
except Exception:
continue
# Définir l'échéance - le Reglement est déjà lié via la factory
# Définir l'échéance
try:
reg_ech.Echeance = echeance
logger.info(" Echeance définie")
@ -379,7 +540,6 @@ def _executer_reglement_com(
logger.info(" Process() réussi!")
return str(numero) if numero else None
else:
# Vérifier les erreurs
try:
errors = process.Errors
if errors:
@ -400,18 +560,15 @@ def _executer_reglement_com(
try:
logger.info("Tentative via modification directe de l'échéance...")
# L'échéance a un attribut Reglement qui est le mode de règlement
mode_obj = getattr(echeance, "Reglement", None)
if mode_obj:
attrs = [a for a in dir(mode_obj) if not a.startswith("_")]
logger.info(f" Attributs Reglement échéance: {attrs[:15]}...")
# Vérifier si l'échéance a FactoryDocumentReglementEcheance
factory_reg_ech = getattr(echeance, "FactoryDocumentReglementEcheance", None)
if factory_reg_ech:
logger.info(" FactoryDocumentReglementEcheance trouvée sur échéance")
# Créer le lien depuis l'échéance
reg_ech = factory_reg_ech.Create()
for iface in [
@ -425,15 +582,12 @@ def _executer_reglement_com(
except Exception:
continue
# Ici, l'échéance devrait déjà être liée
# Il faut définir le règlement
try:
# Créer un nouveau règlement pour ce lien
factory_reg = self.cial.FactoryDocumentReglement
new_reg = factory_reg.Create()
new_reg = win32com.client.CastTo(new_reg, "IBODocumentReglement")
# Configurer minimalement
# Configurer
journal_factory = self.cial.CptaApplication.FactoryJournal
journal_persist = journal_factory.ReadNumero(code_journal)
if journal_persist:
@ -449,14 +603,37 @@ def _executer_reglement_com(
new_reg.RG_Montant = montant
new_reg.RG_Impute = 1
# Écrire le règlement
# Devise si non EUR
if devise_code != 0:
try:
new_reg.RG_Devise = devise_code
new_reg.RG_Cours = cours_devise
new_reg.RG_MontantDev = montant * cours_devise
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
cg_persist = cg_factory.ReadNumero(compte_general)
if cg_persist:
new_reg.CompteG = cg_persist
except Exception:
pass
new_reg.Write()
logger.info(
f" Nouveau règlement créé: {getattr(new_reg, 'RG_Piece', None)}"
)
# Assigner au lien - ici on doit peut-être utiliser un autre attribut
# Puisque reg_ech.Reglement n'est pas settable, essayons via SetDefault
try:
reg_ech.SetDefault()
except Exception:
@ -770,6 +947,315 @@ def lire_reglements_client(
}
def lire_modes_reglement(self) -> List[Dict]:
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
with self._get_sql_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT
cbIndice,
R_Intitule,
R_Code,
R_ModePaieDebit,
R_ModePaieCredit
FROM P_REGLEMENT
WHERE R_Intitule IS NOT NULL AND LTRIM(RTRIM(R_Intitule)) <> ''
ORDER BY cbIndice
""")
modes = []
for row in cursor.fetchall():
intitule = (row[1] or "").strip()
if intitule: # Ne garder que ceux avec un intitulé
modes.append(
{
"code": row[0], # cbIndice
"intitule": intitule,
"code_sage": (row[2] or "").strip(),
"mode_paie_debit": row[3] or 0,
"mode_paie_credit": row[4] or 0,
}
)
return modes
def _get_modes_reglement_standards() -> List[Dict]:
"""Modes de règlement standards Sage si P_REGLEMENT non configuré"""
return [
{"code": 0, "intitule": "Chèque", "type": "banque"},
{"code": 1, "intitule": "Virement", "type": "banque"},
{"code": 2, "intitule": "Espèces", "type": "caisse"},
{"code": 3, "intitule": "LCR Acceptée", "type": "banque"},
{"code": 4, "intitule": "LCR non acceptée", "type": "banque"},
{"code": 5, "intitule": "BOR", "type": "banque"},
{"code": 6, "intitule": "Prélèvement", "type": "banque"},
{"code": 7, "intitule": "Carte bancaire", "type": "banque"},
{"code": 8, "intitule": "Bon d'achat", "type": "autre"},
]
def lire_devises(self) -> List[Dict]:
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
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'
""")
has_f_devise = cursor.fetchone()[0] > 0
if has_f_devise:
cursor.execute("""
SELECT
D_Code,
D_Intitule,
D_Sigle,
D_CoursActuel
FROM F_DEVISE
ORDER BY D_Code
""")
devises = []
for row in cursor.fetchall():
devises.append(
{
"code": row[0],
"intitule": (row[1] or "").strip(),
"sigle": (row[2] or "").strip(),
"cours_actuel": float(row[3] or 1.0),
}
)
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,
"intitule": "Euro",
"sigle": "EUR",
"cours_actuel": 1.0,
"est_principale": devise_principale == 0,
},
{
"code": 1,
"intitule": "Dollar US",
"sigle": "USD",
"cours_actuel": 1.0,
"est_principale": devise_principale == 1,
},
{
"code": 2,
"intitule": "Livre Sterling",
"sigle": "GBP",
"cours_actuel": 1.0,
"est_principale": devise_principale == 2,
},
{
"code": 3,
"intitule": "Franc Suisse",
"sigle": "CHF",
"cours_actuel": 1.0,
"est_principale": devise_principale == 3,
},
]
return devises_standards
def lire_journaux_tresorerie(self) -> List[Dict]:
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
with self._get_sql_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT
JO_Num,
JO_Intitule,
CG_Num,
JO_Type,
JO_Reglement,
JO_Sommeil
FROM F_JOURNAUX
WHERE JO_Type = 2
AND JO_Reglement = 1
AND (JO_Sommeil = 0 OR JO_Sommeil IS NULL)
ORDER BY JO_Num
""")
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"):
type_journal = "banque"
else:
type_journal = "tresorerie"
journaux.append(
{
"code": row[0].strip(),
"intitule": (row[1] or "").strip(),
"compte_general": compte_general,
"type": type_journal,
}
)
return journaux
def lire_comptes_generaux(
self, prefixe: str = None, type_compte: str = None
) -> List[Dict]:
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
# Mapping type -> préfixes de comptes
prefixes_map = {
"client": ["411"],
"fournisseur": ["401"],
"banque": ["51"],
"caisse": ["53"],
"tva_collectee": ["4457"],
"tva_deductible": ["4456"],
"tva": ["445"],
"produit": ["7"],
"charge": ["6"],
}
with self._get_sql_connection() as conn:
cursor = conn.cursor()
query = """
SELECT
CG_Num,
CG_Intitule,
CG_Type,
CG_Raccourci,
CG_Sommeil
FROM F_COMPTEG
WHERE (CG_Sommeil = 0 OR CG_Sommeil IS NULL)
"""
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])
query += f" AND ({conditions})"
params.extend([f"{p}%" for p in prefixes])
elif prefixe:
query += " AND CG_Num LIKE ?"
params.append(f"{prefixe}%")
query += " ORDER BY CG_Num"
cursor.execute(query, params)
comptes = []
for row in cursor.fetchall():
comptes.append(
{
"numero": row[0].strip(),
"intitule": (row[1] or "").strip(),
"type": row[2] or 0,
"raccourci": (row[3] or "").strip(),
}
)
return comptes
def lire_tva_taux(self) -> List[Dict]:
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
with self._get_sql_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT
TA_No,
TA_Code,
TA_Intitule,
TA_Taux,
TA_TTaux,
TA_Type,
TA_Sens,
CG_Num,
TA_Assujet
FROM F_TAXE
ORDER BY TA_No
""")
taux = []
for row in cursor.fetchall():
taux.append(
{
"numero": row[0],
"code": (row[1] or "").strip(),
"intitule": (row[2] or "").strip(),
"taux": float(row[3] or 0),
"type_taux": row[4] or 0, # 0=Pourcentage, 1=Montant
"type": row[5] or 0, # 0=Collectée, 1=Déductible, etc.
"sens": row[6] or 0, # 0=Vente, 1=Achat
"compte_general": (row[7] or "").strip(),
"assujetti": row[8] or 0,
}
)
return taux
def lire_parametres_encaissement(self) -> Dict:
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
with self._get_sql_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT
D_TVAEncReg,
D_TVAEncAffect,
N_DeviseCompte
FROM P_DOSSIER
""")
row = cursor.fetchone()
if row:
return {
"tva_encaissement_regime": row[0] or 0,
"tva_encaissement_affectation": row[1] or 0,
"tva_encaissement_actif": (row[0] or 0) > 0,
"devise_compte": row[2] or 0,
}
return {
"tva_encaissement_regime": 0,
"tva_encaissement_affectation": 0,
"tva_encaissement_actif": False,
"devise_compte": 0,
}
__all__ = [
"ModeReglement",
"lire_journaux_banque",
@ -779,4 +1265,11 @@ __all__ = [
"regler_factures_client",
"lire_reglements_facture",
"lire_reglements_client",
"lire_modes_reglement",
"lire_devises",
"lire_journaux_tresorerie",
"lire_comptes_generaux",
"lire_tva_taux",
"lire_parametres_encaissement",
"_get_modes_reglement_standards",
]