Merge branch 'develop'
This commit is contained in:
commit
fdf359738b
6 changed files with 642 additions and 41 deletions
368
api.py
368
api.py
|
|
@ -1,6 +1,6 @@
|
||||||
from fastapi import FastAPI, HTTPException, Path, Query, Depends, status, Body
|
from fastapi import FastAPI, HTTPException, Path, Query, Depends, status, Body
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse, HTMLResponse, Response
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from pydantic import BaseModel, Field, EmailStr
|
from pydantic import BaseModel, Field, EmailStr
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -2874,6 +2875,371 @@ async def obtenir_informations_societe():
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/societe/logo", tags=["Société"])
|
||||||
|
async def obtenir_logo_societe():
|
||||||
|
"""Retourne le logo en tant qu'image directe"""
|
||||||
|
try:
|
||||||
|
societe = sage_client.lire_informations_societe()
|
||||||
|
|
||||||
|
if not societe or not societe.get("logo_base64"):
|
||||||
|
raise HTTPException(404, "Logo introuvable")
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
image_data = base64.b64decode(societe["logo_base64"])
|
||||||
|
|
||||||
|
return Response(content=image_data, media_type=societe["logo_content_type"])
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur récupération logo: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/societe/preview", response_class=HTMLResponse, tags=["Société"])
|
||||||
|
async def preview_societe():
|
||||||
|
"""Page HTML pour visualiser les infos société avec logo"""
|
||||||
|
try:
|
||||||
|
societe = sage_client.lire_informations_societe()
|
||||||
|
|
||||||
|
if not societe:
|
||||||
|
return "<h1>Société introuvable</h1>"
|
||||||
|
|
||||||
|
logo_html = ""
|
||||||
|
if societe.get("logo_base64"):
|
||||||
|
logo_html = f'<img src="data:{societe["logo_content_type"]};base64,{societe["logo_base64"]}" style="max-width: 300px; border: 1px solid #ccc; padding: 10px;">'
|
||||||
|
else:
|
||||||
|
logo_html = "<p>Aucun logo disponible</p>"
|
||||||
|
|
||||||
|
html = f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Informations Société</title>
|
||||||
|
<style>
|
||||||
|
body {{ font-family: Arial, sans-serif; margin: 40px; }}
|
||||||
|
.container {{ max-width: 800px; }}
|
||||||
|
.logo {{ margin: 20px 0; }}
|
||||||
|
.info {{ margin: 10px 0; }}
|
||||||
|
.label {{ font-weight: bold; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Informations Société</h1>
|
||||||
|
|
||||||
|
<div class="logo">
|
||||||
|
<h2>Logo</h2>
|
||||||
|
{logo_html}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Raison sociale:</span> {societe["raison_sociale"]}
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">SIRET:</span> {societe["siret"] or "N/A"}
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Adresse:</span> {societe["adresse"] or "N/A"}
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Code postal:</span> {societe["code_postal"] or "N/A"}
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Ville:</span> {societe["ville"] or "N/A"}
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Email:</span> {societe["email"] or "N/A"}
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Téléphone:</span> {societe["telephone"] or "N/A"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
return html
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"<h1>Erreur</h1><p>{str(e)}</p>"
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/factures/{numero_facture}/valider", status_code=200, tags=["Factures"])
|
||||||
|
async def valider_facture(
|
||||||
|
numero_facture: str,
|
||||||
|
_: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
resultat = sage_client.valider_facture(numero_facture)
|
||||||
|
logger.info(
|
||||||
|
f"Facture {numero_facture} validée: {resultat.get('action_effectuee')}"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": resultat.get("message", "Facture validée"),
|
||||||
|
"data": resultat,
|
||||||
|
}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur validation facture {numero_facture}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/factures/{numero_facture}/devalider", status_code=200, tags=["Factures"])
|
||||||
|
async def devalider_facture(
|
||||||
|
numero_facture: str,
|
||||||
|
_: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
resultat = sage_client.devalider_facture(numero_facture)
|
||||||
|
logger.info(
|
||||||
|
f"Facture {numero_facture} dévalidée: {resultat.get('action_effectuee')}"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": resultat.get("message", "Facture dévalidée"),
|
||||||
|
"data": resultat,
|
||||||
|
}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur dévalidation facture {numero_facture}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/factures/{numero_facture}/statut-validation", tags=["Factures"])
|
||||||
|
async def get_statut_validation_facture(
|
||||||
|
numero_facture: str,
|
||||||
|
_: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
resultat = sage_client.get_statut_validation(numero_facture)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": resultat,
|
||||||
|
}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture statut {numero_facture}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@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=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 "",
|
||||||
|
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"Journal: {reglement.code_journal} - Mode: {reglement.mode_reglement}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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("/journaux/banque", tags=["Règlements"])
|
||||||
|
async def get_journaux_banque():
|
||||||
|
try:
|
||||||
|
resultat = sage_client.get_journaux_banque()
|
||||||
|
return {"success": True, "data": resultat}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture journaux: {e}")
|
||||||
|
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"])
|
@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),
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ class LocalDocumentStatus(str, Enum):
|
||||||
|
|
||||||
class SageDocumentType(int, Enum):
|
class SageDocumentType(int, Enum):
|
||||||
DEVIS = 0
|
DEVIS = 0
|
||||||
BON_COMMANDE = 10
|
BON_COMMANDE = 10
|
||||||
PREPARATION = 20
|
PREPARATION = 20
|
||||||
BON_LIVRAISON = 30
|
BON_LIVRAISON = 30
|
||||||
BON_RETOUR = 40
|
BON_RETOUR = 40
|
||||||
|
|
@ -61,8 +61,7 @@ class SageDocumentType(int, Enum):
|
||||||
class UniversignTransaction(Base):
|
class UniversignTransaction(Base):
|
||||||
__tablename__ = "universign_transactions"
|
__tablename__ = "universign_transactions"
|
||||||
|
|
||||||
# === IDENTIFIANTS ===
|
id = Column(String(36), primary_key=True)
|
||||||
id = Column(String(36), primary_key=True) # UUID local
|
|
||||||
transaction_id = Column(
|
transaction_id = Column(
|
||||||
String(255),
|
String(255),
|
||||||
unique=True,
|
unique=True,
|
||||||
|
|
@ -71,7 +70,6 @@ class UniversignTransaction(Base):
|
||||||
comment="ID Universign (ex: tr_abc123)",
|
comment="ID Universign (ex: tr_abc123)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# === LIEN AVEC LE DOCUMENT SAGE ===
|
|
||||||
sage_document_id = Column(
|
sage_document_id = Column(
|
||||||
String(50),
|
String(50),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -82,7 +80,6 @@ class UniversignTransaction(Base):
|
||||||
SQLEnum(SageDocumentType), nullable=False, comment="Type de document Sage"
|
SQLEnum(SageDocumentType), nullable=False, comment="Type de document Sage"
|
||||||
)
|
)
|
||||||
|
|
||||||
# === STATUTS UNIVERSIGN (SOURCE DE VÉRITÉ) ===
|
|
||||||
universign_status = Column(
|
universign_status = Column(
|
||||||
SQLEnum(UniversignTransactionStatus),
|
SQLEnum(UniversignTransactionStatus),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -94,7 +91,6 @@ class UniversignTransaction(Base):
|
||||||
DateTime, nullable=True, comment="Dernière MAJ du statut Universign"
|
DateTime, nullable=True, comment="Dernière MAJ du statut Universign"
|
||||||
)
|
)
|
||||||
|
|
||||||
# === STATUT LOCAL (DÉDUIT) ===
|
|
||||||
local_status = Column(
|
local_status = Column(
|
||||||
SQLEnum(LocalDocumentStatus),
|
SQLEnum(LocalDocumentStatus),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -103,7 +99,6 @@ class UniversignTransaction(Base):
|
||||||
comment="Statut métier simplifié pour l'UI",
|
comment="Statut métier simplifié pour l'UI",
|
||||||
)
|
)
|
||||||
|
|
||||||
# === URLS ET MÉTADONNÉES UNIVERSIGN ===
|
|
||||||
signer_url = Column(Text, nullable=True, comment="URL de signature")
|
signer_url = Column(Text, nullable=True, comment="URL de signature")
|
||||||
document_url = Column(Text, nullable=True, comment="URL du document signé")
|
document_url = Column(Text, nullable=True, comment="URL du document signé")
|
||||||
|
|
||||||
|
|
@ -125,17 +120,14 @@ class UniversignTransaction(Base):
|
||||||
|
|
||||||
certificate_url = Column(Text, nullable=True, comment="URL du certificat")
|
certificate_url = Column(Text, nullable=True, comment="URL du certificat")
|
||||||
|
|
||||||
# === SIGNATAIRES ===
|
|
||||||
signers_data = Column(
|
signers_data = Column(
|
||||||
Text, nullable=True, comment="JSON des signataires (snapshot)"
|
Text, nullable=True, comment="JSON des signataires (snapshot)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# === INFORMATIONS MÉTIER ===
|
|
||||||
requester_email = Column(String(255), nullable=True)
|
requester_email = Column(String(255), nullable=True)
|
||||||
requester_name = Column(String(255), nullable=True)
|
requester_name = Column(String(255), nullable=True)
|
||||||
document_name = Column(String(500), nullable=True)
|
document_name = Column(String(500), nullable=True)
|
||||||
|
|
||||||
# === DATES CLÉS ===
|
|
||||||
created_at = Column(
|
created_at = Column(
|
||||||
DateTime,
|
DateTime,
|
||||||
default=datetime.now,
|
default=datetime.now,
|
||||||
|
|
@ -150,14 +142,12 @@ class UniversignTransaction(Base):
|
||||||
expired_at = Column(DateTime, nullable=True)
|
expired_at = Column(DateTime, nullable=True)
|
||||||
canceled_at = Column(DateTime, nullable=True)
|
canceled_at = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
# === SYNCHRONISATION ===
|
|
||||||
last_synced_at = Column(
|
last_synced_at = Column(
|
||||||
DateTime, nullable=True, comment="Dernière sync réussie avec Universign"
|
DateTime, nullable=True, comment="Dernière sync réussie avec Universign"
|
||||||
)
|
)
|
||||||
sync_attempts = Column(Integer, default=0, comment="Nombre de tentatives de sync")
|
sync_attempts = Column(Integer, default=0, comment="Nombre de tentatives de sync")
|
||||||
sync_error = Column(Text, nullable=True)
|
sync_error = Column(Text, nullable=True)
|
||||||
|
|
||||||
# === FLAGS ===
|
|
||||||
is_test = Column(
|
is_test = Column(
|
||||||
Boolean, default=False, comment="Transaction en environnement .alpha"
|
Boolean, default=False, comment="Transaction en environnement .alpha"
|
||||||
)
|
)
|
||||||
|
|
@ -166,7 +156,6 @@ class UniversignTransaction(Base):
|
||||||
)
|
)
|
||||||
webhook_received = Column(Boolean, default=False, comment="Webhook Universign reçu")
|
webhook_received = Column(Boolean, default=False, comment="Webhook Universign reçu")
|
||||||
|
|
||||||
# === RELATION ===
|
|
||||||
signers = relationship(
|
signers = relationship(
|
||||||
"UniversignSigner", back_populates="transaction", cascade="all, delete-orphan"
|
"UniversignSigner", back_populates="transaction", cascade="all, delete-orphan"
|
||||||
)
|
)
|
||||||
|
|
@ -174,7 +163,6 @@ class UniversignTransaction(Base):
|
||||||
"UniversignSyncLog", back_populates="transaction", cascade="all, delete-orphan"
|
"UniversignSyncLog", back_populates="transaction", cascade="all, delete-orphan"
|
||||||
)
|
)
|
||||||
|
|
||||||
# === INDEXES COMPOSITES ===
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
Index("idx_sage_doc", "sage_document_id", "sage_document_type"),
|
Index("idx_sage_doc", "sage_document_id", "sage_document_type"),
|
||||||
Index("idx_sync_status", "needs_sync", "universign_status"),
|
Index("idx_sync_status", "needs_sync", "universign_status"),
|
||||||
|
|
@ -190,10 +178,6 @@ class UniversignTransaction(Base):
|
||||||
|
|
||||||
|
|
||||||
class UniversignSigner(Base):
|
class UniversignSigner(Base):
|
||||||
"""
|
|
||||||
Détail de chaque signataire d'une transaction
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "universign_signers"
|
__tablename__ = "universign_signers"
|
||||||
|
|
||||||
id = Column(String(36), primary_key=True)
|
id = Column(String(36), primary_key=True)
|
||||||
|
|
@ -204,33 +188,27 @@ class UniversignSigner(Base):
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# === DONNÉES SIGNATAIRE ===
|
|
||||||
email = Column(String(255), nullable=False, index=True)
|
email = Column(String(255), nullable=False, index=True)
|
||||||
name = Column(String(255), nullable=True)
|
name = Column(String(255), nullable=True)
|
||||||
phone = Column(String(50), nullable=True)
|
phone = Column(String(50), nullable=True)
|
||||||
|
|
||||||
# === STATUT ===
|
|
||||||
status = Column(
|
status = Column(
|
||||||
SQLEnum(UniversignSignerStatus),
|
SQLEnum(UniversignSignerStatus),
|
||||||
default=UniversignSignerStatus.WAITING,
|
default=UniversignSignerStatus.WAITING,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# === ACTIONS ===
|
|
||||||
viewed_at = Column(DateTime, nullable=True)
|
viewed_at = Column(DateTime, nullable=True)
|
||||||
signed_at = Column(DateTime, nullable=True)
|
signed_at = Column(DateTime, nullable=True)
|
||||||
refused_at = Column(DateTime, nullable=True)
|
refused_at = Column(DateTime, nullable=True)
|
||||||
refusal_reason = Column(Text, nullable=True)
|
refusal_reason = Column(Text, nullable=True)
|
||||||
|
|
||||||
# === MÉTADONNÉES ===
|
|
||||||
ip_address = Column(String(45), nullable=True)
|
ip_address = Column(String(45), nullable=True)
|
||||||
user_agent = Column(Text, nullable=True)
|
user_agent = Column(Text, nullable=True)
|
||||||
signature_method = Column(String(50), nullable=True)
|
signature_method = Column(String(50), nullable=True)
|
||||||
|
|
||||||
# === ORDRE ===
|
|
||||||
order_index = Column(Integer, default=0)
|
order_index = Column(Integer, default=0)
|
||||||
|
|
||||||
# === RELATION ===
|
|
||||||
transaction = relationship("UniversignTransaction", back_populates="signers")
|
transaction = relationship("UniversignTransaction", back_populates="signers")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
@ -238,10 +216,6 @@ class UniversignSigner(Base):
|
||||||
|
|
||||||
|
|
||||||
class UniversignSyncLog(Base):
|
class UniversignSyncLog(Base):
|
||||||
"""
|
|
||||||
Journal de toutes les synchronisations (audit trail)
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "universign_sync_logs"
|
__tablename__ = "universign_sync_logs"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
|
@ -252,22 +226,18 @@ class UniversignSyncLog(Base):
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# === SYNC INFO ===
|
|
||||||
sync_type = Column(String(50), nullable=False, comment="webhook, polling, manual")
|
sync_type = Column(String(50), nullable=False, comment="webhook, polling, manual")
|
||||||
sync_timestamp = Column(DateTime, default=datetime.now, nullable=False, index=True)
|
sync_timestamp = Column(DateTime, default=datetime.now, nullable=False, index=True)
|
||||||
|
|
||||||
# === CHANGEMENTS DÉTECTÉS ===
|
|
||||||
previous_status = Column(String(50), nullable=True)
|
previous_status = Column(String(50), nullable=True)
|
||||||
new_status = Column(String(50), nullable=True)
|
new_status = Column(String(50), nullable=True)
|
||||||
changes_detected = Column(Text, nullable=True, comment="JSON des changements")
|
changes_detected = Column(Text, nullable=True, comment="JSON des changements")
|
||||||
|
|
||||||
# === RÉSULTAT ===
|
|
||||||
success = Column(Boolean, default=True)
|
success = Column(Boolean, default=True)
|
||||||
error_message = Column(Text, nullable=True)
|
error_message = Column(Text, nullable=True)
|
||||||
http_status_code = Column(Integer, nullable=True)
|
http_status_code = Column(Integer, nullable=True)
|
||||||
response_time_ms = Column(Integer, nullable=True)
|
response_time_ms = Column(Integer, nullable=True)
|
||||||
|
|
||||||
# === RELATION ===
|
|
||||||
transaction = relationship("UniversignTransaction", back_populates="sync_logs")
|
transaction = relationship("UniversignTransaction", back_populates="sync_logs")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
@ -287,7 +257,6 @@ class UniversignConfig(Base):
|
||||||
api_url = Column(String(500), nullable=False)
|
api_url = Column(String(500), nullable=False)
|
||||||
api_key = Column(String(500), nullable=False, comment="À chiffrer")
|
api_key = Column(String(500), nullable=False, comment="À chiffrer")
|
||||||
|
|
||||||
# === OPTIONS ===
|
|
||||||
webhook_url = Column(String(500), nullable=True)
|
webhook_url = Column(String(500), nullable=True)
|
||||||
webhook_secret = Column(String(255), nullable=True)
|
webhook_secret = Column(String(255), nullable=True)
|
||||||
|
|
||||||
|
|
|
||||||
153
sage_client.py
153
sage_client.py
|
|
@ -431,6 +431,159 @@ 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 valider_facture(self, numero_facture: str) -> dict:
|
||||||
|
response = self._post(f"/sage/factures/{numero_facture}/valider", {})
|
||||||
|
return response.get("data", {})
|
||||||
|
|
||||||
|
def devalider_facture(self, numero_facture: str) -> dict:
|
||||||
|
response = self._post(f"/sage/factures/{numero_facture}/devalider", {})
|
||||||
|
return response.get("data", {})
|
||||||
|
|
||||||
|
def get_statut_validation(self, numero_facture: str) -> dict:
|
||||||
|
response = self._get(f"/sage/factures/{numero_facture}/statut-validation")
|
||||||
|
return response.get("data", {})
|
||||||
|
|
||||||
|
def regler_facture(
|
||||||
|
self,
|
||||||
|
numero_facture: str,
|
||||||
|
montant: float,
|
||||||
|
mode_reglement: int = 0,
|
||||||
|
date_reglement: str = 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,
|
||||||
|
) -> dict:
|
||||||
|
"""Règle une facture"""
|
||||||
|
payload = {
|
||||||
|
"montant": montant,
|
||||||
|
"mode_reglement": mode_reglement,
|
||||||
|
"reference": reference,
|
||||||
|
"libelle": libelle,
|
||||||
|
"devise_code": devise_code,
|
||||||
|
"cours_devise": cours_devise,
|
||||||
|
"tva_encaissement": tva_encaissement,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Champs optionnels
|
||||||
|
if date_reglement:
|
||||||
|
payload["date_reglement"] = date_reglement
|
||||||
|
if code_journal:
|
||||||
|
payload["code_journal"] = code_journal
|
||||||
|
if compte_general:
|
||||||
|
payload["compte_general"] = compte_general
|
||||||
|
|
||||||
|
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 = 0,
|
||||||
|
date_reglement: str = None,
|
||||||
|
reference: str = "",
|
||||||
|
libelle: str = "",
|
||||||
|
code_journal: str = None,
|
||||||
|
numeros_factures: list = None,
|
||||||
|
devise_code: int = 0,
|
||||||
|
cours_devise: float = 1.0,
|
||||||
|
tva_encaissement: bool = False,
|
||||||
|
) -> 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,
|
||||||
|
"devise_code": devise_code,
|
||||||
|
"cours_devise": cours_devise,
|
||||||
|
"tva_encaissement": tva_encaissement,
|
||||||
|
}
|
||||||
|
|
||||||
|
if date_reglement:
|
||||||
|
payload["date_reglement"] = date_reglement
|
||||||
|
if code_journal:
|
||||||
|
payload["code_journal"] = code_journal
|
||||||
|
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 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:
|
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
|
||||||
|
|
|
||||||
116
schemas/documents/reglements.py
Normal file
116
schemas/documents/reglements.py
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
from typing import List, Optional
|
||||||
|
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 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"
|
||||||
|
)
|
||||||
|
compte_general: Optional[str] = Field(None)
|
||||||
|
|
||||||
|
@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",
|
||||||
|
"date_reglement": "2024-01-01",
|
||||||
|
"libelle": "Règlement multiple",
|
||||||
|
"tva_encaissement": False,
|
||||||
|
"devise_code": 0,
|
||||||
|
"cours_devise": 1.0,
|
||||||
|
"date_echeance": "2024-01-31",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ReglementMultipleCreate(BaseModel):
|
||||||
|
"""Requête de règlement multiple côté VPS"""
|
||||||
|
|
||||||
|
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):
|
||||||
|
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"],
|
||||||
|
"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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,14 +9,12 @@ class ExerciceComptable(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class SocieteInfo(BaseModel):
|
class SocieteInfo(BaseModel):
|
||||||
# Identification
|
|
||||||
raison_sociale: str
|
raison_sociale: str
|
||||||
numero_dossier: str
|
numero_dossier: str
|
||||||
siret: Optional[str] = None
|
siret: Optional[str] = None
|
||||||
code_ape: Optional[str] = None
|
code_ape: Optional[str] = None
|
||||||
numero_tva: Optional[str] = None
|
numero_tva: Optional[str] = None
|
||||||
|
|
||||||
# Adresse
|
|
||||||
adresse: Optional[str] = None
|
adresse: Optional[str] = None
|
||||||
complement_adresse: Optional[str] = None
|
complement_adresse: Optional[str] = None
|
||||||
code_postal: Optional[str] = None
|
code_postal: Optional[str] = None
|
||||||
|
|
@ -24,27 +22,25 @@ class SocieteInfo(BaseModel):
|
||||||
code_region: Optional[str] = None
|
code_region: Optional[str] = None
|
||||||
pays: Optional[str] = None
|
pays: Optional[str] = None
|
||||||
|
|
||||||
# Contacts
|
|
||||||
telephone: Optional[str] = None
|
telephone: Optional[str] = None
|
||||||
telecopie: Optional[str] = None
|
telecopie: Optional[str] = None
|
||||||
email: Optional[str] = None
|
email: Optional[str] = None
|
||||||
email_societe: Optional[str] = None
|
email_societe: Optional[str] = None
|
||||||
site_web: Optional[str] = None
|
site_web: Optional[str] = None
|
||||||
|
|
||||||
# Informations juridiques
|
|
||||||
capital: float = 0.0
|
capital: float = 0.0
|
||||||
forme_juridique: Optional[str] = None
|
forme_juridique: Optional[str] = None
|
||||||
|
|
||||||
# Exercices comptables
|
|
||||||
exercices: List[ExerciceComptable] = []
|
exercices: List[ExerciceComptable] = []
|
||||||
|
|
||||||
# Configuration
|
|
||||||
devise_compte: int = 0
|
devise_compte: int = 0
|
||||||
devise_equivalent: int = 0
|
devise_equivalent: int = 0
|
||||||
longueur_compte_general: int = 0
|
longueur_compte_general: int = 0
|
||||||
longueur_compte_analytique: int = 0
|
longueur_compte_analytique: int = 0
|
||||||
regime_fec: int = 0
|
regime_fec: int = 0
|
||||||
|
|
||||||
# Autres
|
|
||||||
base_modele: Optional[str] = None
|
base_modele: Optional[str] = None
|
||||||
marqueur: int = 0
|
marqueur: int = 0
|
||||||
|
|
||||||
|
logo_base64: Optional[str] = None
|
||||||
|
logo_content_type: Optional[str] = None
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue