feat(payments): add payment functionality for invoices
This commit is contained in:
parent
eedc628a5f
commit
25be0bd569
4 changed files with 281 additions and 0 deletions
141
api.py
141
api.py
|
|
@ -71,6 +71,7 @@ from schemas import (
|
||||||
ContactCreate,
|
ContactCreate,
|
||||||
ContactUpdate,
|
ContactUpdate,
|
||||||
)
|
)
|
||||||
|
from schemas.documents.reglements import ReglementFactureCreate, ReglementMultipleCreate
|
||||||
from schemas.tiers.commercial import (
|
from schemas.tiers.commercial import (
|
||||||
CollaborateurCreate,
|
CollaborateurCreate,
|
||||||
CollaborateurDetails,
|
CollaborateurDetails,
|
||||||
|
|
@ -2965,6 +2966,146 @@ async def preview_societe():
|
||||||
return f"<h1>Erreur</h1><p>{str(e)}</p>"
|
return f"<h1>Erreur</h1><p>{str(e)}</p>"
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/factures/{numero_facture}/regler", status_code=200, tags=["Règlements"])
|
||||||
|
async def regler_facture(
|
||||||
|
numero_facture: str,
|
||||||
|
reglement: ReglementFactureCreate,
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
resultat = sage_client.regler_facture(
|
||||||
|
numero_facture=numero_facture,
|
||||||
|
montant=reglement.montant,
|
||||||
|
mode_reglement=reglement.mode_reglement,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Règlement facture {numero_facture}: {reglement.montant}€ - "
|
||||||
|
f"Solde: {resultat.get('solde_restant', 0)}€"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Règlement effectué avec succès",
|
||||||
|
"data": resultat,
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur règlement facture {numero_facture}: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/reglements/multiple", status_code=200, tags=["Règlements"])
|
||||||
|
async def regler_factures_multiple(
|
||||||
|
reglement: ReglementMultipleCreate,
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
resultat = sage_client.regler_factures_client(
|
||||||
|
client_code=reglement.client_id,
|
||||||
|
montant_total=reglement.montant_total,
|
||||||
|
mode_reglement=reglement.mode_reglement,
|
||||||
|
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,
|
||||||
|
numeros_factures=reglement.numeros_factures,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Règlement multiple client {reglement.client_id}: "
|
||||||
|
f"{resultat.get('montant_effectif', 0)}€ sur {resultat.get('nb_factures_reglees', 0)} facture(s)"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Règlements effectués avec succès",
|
||||||
|
"data": resultat,
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur règlement multiple {reglement.client_id}: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/factures/{numero_facture}/reglements", tags=["Règlements"])
|
||||||
|
async def get_reglements_facture(
|
||||||
|
numero_facture: str,
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
resultat = sage_client.get_reglements_facture(numero_facture)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": resultat,
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture règlements {numero_facture}: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/clients/{client_id}/reglements", tags=["Règlements"])
|
||||||
|
async def get_reglements_client(
|
||||||
|
client_id: str,
|
||||||
|
date_debut: Optional[datetime] = Query(None, description="Date début"),
|
||||||
|
date_fin: Optional[datetime] = Query(None, description="Date fin"),
|
||||||
|
inclure_soldees: bool = Query(True, description="Inclure les factures soldées"),
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
resultat = sage_client.get_reglements_client(
|
||||||
|
client_code=client_id,
|
||||||
|
date_debut=date_debut.isoformat() if date_debut else None,
|
||||||
|
date_fin=date_fin.isoformat() if date_fin else None,
|
||||||
|
inclure_soldees=inclure_soldees,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": resultat,
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture règlements client {client_id}: {e}")
|
||||||
|
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("/health", tags=["System"])
|
@app.get("/health", tags=["System"])
|
||||||
async def health_check(
|
async def health_check(
|
||||||
sage: SageGatewayClient = Depends(get_sage_client_for_user),
|
sage: SageGatewayClient = Depends(get_sage_client_for_user),
|
||||||
|
|
|
||||||
|
|
@ -431,6 +431,80 @@ class SageGatewayClient:
|
||||||
"""Lit les informations de la société depuis P_DOSSIER"""
|
"""Lit les informations de la société depuis P_DOSSIER"""
|
||||||
return self._get("/sage/societe/info").get("data")
|
return self._get("/sage/societe/info").get("data")
|
||||||
|
|
||||||
|
def regler_facture(
|
||||||
|
self,
|
||||||
|
numero_facture: str,
|
||||||
|
montant: float,
|
||||||
|
mode_reglement: int = 2,
|
||||||
|
date_reglement: str = None,
|
||||||
|
reference: str = "",
|
||||||
|
libelle: str = "",
|
||||||
|
code_journal: str = "BEU",
|
||||||
|
) -> dict:
|
||||||
|
"""Règle une facture"""
|
||||||
|
payload = {
|
||||||
|
"montant": montant,
|
||||||
|
"mode_reglement": mode_reglement,
|
||||||
|
"reference": reference,
|
||||||
|
"libelle": libelle,
|
||||||
|
"code_journal": code_journal,
|
||||||
|
}
|
||||||
|
if date_reglement:
|
||||||
|
payload["date_reglement"] = date_reglement
|
||||||
|
|
||||||
|
return self._post(f"/sage/factures/{numero_facture}/regler", payload).get(
|
||||||
|
"data", {}
|
||||||
|
)
|
||||||
|
|
||||||
|
def regler_factures_client(
|
||||||
|
self,
|
||||||
|
client_code: str,
|
||||||
|
montant_total: float,
|
||||||
|
mode_reglement: int = 2,
|
||||||
|
date_reglement: str = None,
|
||||||
|
reference: str = "",
|
||||||
|
libelle: str = "",
|
||||||
|
code_journal: str = "BEU",
|
||||||
|
numeros_factures: list = None,
|
||||||
|
) -> dict:
|
||||||
|
"""Règle plusieurs factures d'un client"""
|
||||||
|
payload = {
|
||||||
|
"client_code": client_code,
|
||||||
|
"montant_total": montant_total,
|
||||||
|
"mode_reglement": mode_reglement,
|
||||||
|
"reference": reference,
|
||||||
|
"libelle": libelle,
|
||||||
|
"code_journal": code_journal,
|
||||||
|
}
|
||||||
|
if date_reglement:
|
||||||
|
payload["date_reglement"] = date_reglement
|
||||||
|
if numeros_factures:
|
||||||
|
payload["numeros_factures"] = numeros_factures
|
||||||
|
|
||||||
|
return self._post("/sage/reglements/multiple", payload).get("data", {})
|
||||||
|
|
||||||
|
def get_reglements_facture(self, numero_facture: str) -> dict:
|
||||||
|
"""Récupère les règlements d'une facture"""
|
||||||
|
return self._get(f"/sage/factures/{numero_facture}/reglements").get("data", {})
|
||||||
|
|
||||||
|
def get_reglements_client(
|
||||||
|
self,
|
||||||
|
client_code: str,
|
||||||
|
date_debut: str = None,
|
||||||
|
date_fin: str = None,
|
||||||
|
inclure_soldees: bool = True,
|
||||||
|
) -> dict:
|
||||||
|
"""Récupère les règlements d'un client"""
|
||||||
|
params = {"inclure_soldees": inclure_soldees}
|
||||||
|
if date_debut:
|
||||||
|
params["date_debut"] = date_debut
|
||||||
|
if date_fin:
|
||||||
|
params["date_fin"] = date_fin
|
||||||
|
|
||||||
|
return self._get(f"/sage/clients/{client_code}/reglements", params=params).get(
|
||||||
|
"data", {}
|
||||||
|
)
|
||||||
|
|
||||||
def refresh_cache(self) -> Dict:
|
def refresh_cache(self) -> Dict:
|
||||||
return self._post("/sage/cache/refresh")
|
return self._post("/sage/cache/refresh")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from datetime import datetime
|
||||||
|
|
||||||
from schemas.documents.ligne_document import LigneDocument
|
from schemas.documents.ligne_document import LigneDocument
|
||||||
|
|
||||||
|
|
||||||
class FactureCreate(BaseModel):
|
class FactureCreate(BaseModel):
|
||||||
client_id: str
|
client_id: str
|
||||||
date_facture: Optional[datetime] = None
|
date_facture: Optional[datetime] = None
|
||||||
|
|
|
||||||
65
schemas/documents/reglements.py
Normal file
65
schemas/documents/reglements.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
from typing import List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
@field_validator("montant")
|
||||||
|
def validate_montant(cls, v):
|
||||||
|
if v <= 0:
|
||||||
|
raise ValueError("Le montant doit être positif")
|
||||||
|
return round(v, 2)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"montant": 375.12,
|
||||||
|
"mode_reglement": 2,
|
||||||
|
"reference": "CHQ-001",
|
||||||
|
"code_journal": "BEU",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@field_validator("client_id", mode="before")
|
||||||
|
def strip_client_id(cls, v):
|
||||||
|
return v.replace("\xa0", "").strip() if v else v
|
||||||
|
|
||||||
|
@field_validator("montant_total")
|
||||||
|
def validate_montant(cls, v):
|
||||||
|
if v <= 0:
|
||||||
|
raise ValueError("Le montant doit être positif")
|
||||||
|
return round(v, 2)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"client_id": "CLI000001",
|
||||||
|
"montant_total": 1000.00,
|
||||||
|
"mode_reglement": 2,
|
||||||
|
"numeros_factures": ["FA00081", "FA00082"],
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue