validation facture et règlement effectuées avec succès, mais approche dangereuse !
This commit is contained in:
parent
7dc5d03c4c
commit
f5c5a87d0d
4 changed files with 679 additions and 70 deletions
101
main.py
101
main.py
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue