feat(payments): enhance payment processing with new endpoints and schema

This commit is contained in:
Fanilo-Nantenaina 2026-01-15 15:49:15 +03:00
parent 457c746706
commit beabefa3f9
3 changed files with 192 additions and 36 deletions

104
api.py
View file

@ -3037,19 +3037,25 @@ async def regler_facture(
try:
resultat = sage_client.regler_facture(
numero_facture=numero_facture,
montant=reglement.montant,
montant=float(reglement.montant),
mode_reglement=reglement.mode_reglement,
code_journal=reglement.code_journal,
date_reglement=reglement.date_reglement.isoformat()
if reglement.date_reglement
else None,
reference=reglement.reference or "",
libelle=reglement.libelle or "",
code_journal=reglement.code_journal,
devise_code=reglement.devise_code,
cours_devise=float(reglement.cours_devise)
if reglement.cours_devise
else 1.0,
tva_encaissement=reglement.tva_encaissement,
compte_general=reglement.compte_general,
)
logger.info(
f"Règlement facture {numero_facture}: {reglement.montant}€ - "
f"Solde: {resultat.get('solde_restant', 0)}"
f"Journal: {reglement.code_journal} - Mode: {reglement.mode_reglement}"
)
return {
@ -3150,24 +3156,6 @@ async def get_reglements_client(
raise HTTPException(500, str(e))
@app.get("/reglements/modes", tags=["Règlements"])
async 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"},
]
},
}
@app.get("/journaux/banque", tags=["Règlements"])
async def get_journaux_banque():
try:
@ -3178,6 +3166,80 @@ async def get_journaux_banque():
raise HTTPException(500, str(e))
@app.get("/reglements/modes", tags=["Référentiels"])
async def get_modes_reglement():
"""Liste des modes de règlement disponibles dans Sage"""
try:
modes = sage_client.get_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("/devises", tags=["Référentiels"])
async def get_devises():
"""Liste des devises disponibles dans Sage"""
try:
devises = sage_client.get_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("/journaux/tresorerie", tags=["Référentiels"])
async def get_journaux_tresorerie():
"""Liste des journaux de trésorerie (banque + caisse)"""
try:
journaux = sage_client.get_journaux_tresorerie()
return {"success": True, "data": {"journaux": journaux}}
except Exception as e:
logger.error(f"Erreur lecture journaux: {e}")
raise HTTPException(500, str(e))
@app.get("/comptes-generaux", tags=["Référentiels"])
async def get_comptes_generaux(
prefixe: Optional[str] = Query(None, description="Filtre par préfixe"),
type_compte: Optional[str] = Query(
None,
description="client | fournisseur | banque | caisse | tva | produit | charge",
),
):
"""Liste des comptes généraux"""
try:
comptes = sage_client.get_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("/tva/taux", tags=["Référentiels"])
async def get_tva_taux():
"""Liste des taux de TVA"""
try:
taux = sage_client.get_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("/parametres/encaissement", tags=["Référentiels"])
async def get_parametres_encaissement():
"""Paramètres TVA sur encaissement"""
try:
params = sage_client.get_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("/health", tags=["System"])
async def health_check(
sage: SageGatewayClient = Depends(get_sage_client_for_user),

View file

@ -520,6 +520,50 @@ class SageGatewayClient:
def get_journaux_banque(self) -> dict:
return self._get("/sage/journaux/banque").get("data", {})
def get_modes_reglement(self) -> List[dict]:
"""Récupère les modes de règlement depuis Sage"""
return self._get("/sage/reglements/modes").get("data", {}).get("modes", [])
def get_devises(self) -> List[dict]:
"""Récupère les devises disponibles"""
return self._get("/sage/devises").get("data", {}).get("devises", [])
def get_journaux_tresorerie(self) -> List[dict]:
"""Récupère les journaux de trésorerie (banque + caisse)"""
return (
self._get("/sage/journaux/tresorerie").get("data", {}).get("journaux", [])
)
def get_comptes_generaux(
self, prefixe: str = None, type_compte: str = None
) -> List[dict]:
"""
Récupère les comptes généraux
Args:
prefixe: Filtre par préfixe (ex: "41", "51")
type_compte: "client", "fournisseur", "banque", "caisse", "tva"
"""
params = {}
if prefixe:
params["prefixe"] = prefixe
if type_compte:
params["type_compte"] = type_compte
return (
self._get("/sage/comptes-generaux", params=params)
.get("data", {})
.get("comptes", [])
)
def get_tva_taux(self) -> List[dict]:
"""Récupère les taux de TVA"""
return self._get("/sage/tva/taux").get("data", {}).get("taux", [])
def get_parametres_encaissement(self) -> dict:
"""Récupère les paramètres TVA sur encaissement"""
return self._get("/sage/parametres/encaissement").get("data", {})
def refresh_cache(self) -> Dict:
return self._post("/sage/cache/refresh")

View file

@ -1,19 +1,46 @@
from pydantic import BaseModel, Field, field_validator
from typing import List, Optional
from datetime import datetime
import logging
from decimal import Decimal
from datetime import date
logger = logging.getLogger(__name__)
class ReglementFactureCreate(BaseModel):
"""Requête de règlement d'une facture côté VPS"""
montant: float = Field(..., gt=0)
mode_reglement: int = Field(default=2, ge=1, le=7)
date_reglement: Optional[datetime] = None
reference: Optional[str] = Field(default="", max_length=35)
libelle: Optional[str] = Field(default="", max_length=69)
code_journal: str = Field(default="BEU", max_length=6)
# Montant et devise
montant: Decimal = Field(..., gt=0, description="Montant à régler")
devise_code: Optional[int] = Field(0, description="Code devise (0=EUR par défaut)")
cours_devise: Optional[Decimal] = Field(1.0, description="Cours de la devise")
# Mode et journal
mode_reglement: int = Field(
..., ge=0, description="Code mode règlement depuis /reglements/modes"
)
code_journal: str = Field(
..., min_length=1, description="Code journal depuis /journaux/tresorerie"
)
# Dates
date_reglement: Optional[date] = Field(
None, description="Date du règlement (défaut: aujourd'hui)"
)
date_echeance: Optional[date] = Field(None, description="Date d'échéance")
# Références
reference: Optional[str] = Field(
"", max_length=17, description="Référence pièce règlement"
)
libelle: Optional[str] = Field(
"", max_length=35, description="Libellé du règlement"
)
# TVA sur encaissement
tva_encaissement: Optional[bool] = Field(
False, description="Appliquer TVA sur encaissement"
)
@field_validator("montant")
def validate_montant(cls, v):
@ -28,6 +55,12 @@ class ReglementFactureCreate(BaseModel):
"mode_reglement": 2,
"reference": "CHQ-001",
"code_journal": "BEU",
"date_reglement": "2024-01-01",
"libelle": "Règlement multiple",
"tva_encaissement": False,
"devise_code": 0,
"cours_devise": 1.0,
"date_echeance": "2024-01-31",
}
}
@ -35,14 +68,23 @@ class ReglementFactureCreate(BaseModel):
class ReglementMultipleCreate(BaseModel):
"""Requête de règlement multiple côté VPS"""
client_id: str
montant_total: float = Field(..., gt=0)
mode_reglement: int = Field(default=2, ge=1, le=7)
date_reglement: Optional[datetime] = None
reference: Optional[str] = Field(default="", max_length=35)
libelle: Optional[str] = Field(default="", max_length=69)
code_journal: str = Field(default="BEU", max_length=6)
numeros_factures: Optional[List[str]] = None
client_id: str = Field(..., description="Code client")
montant_total: Decimal = Field(..., gt=0)
# Même structure que ReglementFactureCreate
devise_code: Optional[int] = Field(0)
cours_devise: Optional[Decimal] = Field(1.0)
mode_reglement: int = Field(...)
code_journal: str = Field(...)
date_reglement: Optional[date] = None
reference: Optional[str] = Field("")
libelle: Optional[str] = Field("")
tva_encaissement: Optional[bool] = Field(False)
# Factures spécifiques (optionnel)
numeros_factures: Optional[List[str]] = Field(
None, description="Si vide, règle les plus anciennes en premier"
)
@field_validator("client_id", mode="before")
def strip_client_id(cls, v):
@ -61,5 +103,13 @@ class ReglementMultipleCreate(BaseModel):
"montant_total": 1000.00,
"mode_reglement": 2,
"numeros_factures": ["FA00081", "FA00082"],
"reference": "CHQ-001",
"code_journal": "BEU",
"date_reglement": "2024-01-01",
"libelle": "Règlement multiple",
"tva_encaissement": False,
"devise_code": 0,
"cours_devise": 1.0,
"date_echeance": "2024-01-31",
}
}