Extracted Pydantic schema

This commit is contained in:
fanilo 2025-12-29 15:07:04 +01:00
parent d6d01fee9f
commit 2ea7fa8371
16 changed files with 2241 additions and 1867 deletions

932
main.py
View file

@ -13,7 +13,43 @@ from config import settings, validate_settings
from sage_connector import SageConnector from sage_connector import SageConnector
import pyodbc import pyodbc
import os import os
from schemas import TiersListRequest from schemas import (
TiersListRequest,
ContactCreateRequest,
ContactDeleteRequest,
ContactGetRequest,
ContactListRequest,
ContactUpdateRequest,
ClientCreateRequest,
ClientUpdateGatewayRequest,
FiltreRequest,
ChampLibreRequest,
CodeRequest,
TransformationRequest,
TypeDocument,
DevisRequest,
DocumentGetRequest,
StatutRequest,
TypeTiers,
FournisseurCreateRequest,
FournisseurUpdateGatewayRequest,
AvoirCreateGatewayRequest,
AvoirUpdateGatewayRequest,
CommandeCreateRequest,
CommandeUpdateGatewayRequest,
FactureCreateGatewayRequest,
FactureUpdateGatewayRequest,
LivraisonCreateGatewayRequest,
LivraisonUpdateGatewayRequest,
ArticleCreateRequest,
ArticleUpdateGatewayRequest,
MouvementStockLigneRequest,
EntreeStockRequest,
SortieStockRequest,
FamilleCreate,
PDFGenerationRequest,
DevisUpdateGatewayRequest
)
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@ -22,882 +58,6 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TypeDocument(int, Enum):
DEVIS = 0
BON_LIVRAISON = 1
BON_RETOUR = 2
COMMANDE = 3
PREPARATION = 4
FACTURE = 5
class DocumentGetRequest(BaseModel):
numero: str
type_doc: int
class FiltreRequest(BaseModel):
filtre: Optional[str] = ""
class CodeRequest(BaseModel):
code: str
class ChampLibreRequest(BaseModel):
doc_id: str
type_doc: int
nom_champ: str
valeur: str
class DevisRequest(BaseModel):
client_id: str
date_devis: Optional[date] = None
date_livraison: Optional[date] = None
reference: Optional[str] = None
lignes: List[Dict]
class TransformationRequest(BaseModel):
numero_source: str
type_source: int
type_cible: int
class StatutRequest(BaseModel):
nouveau_statut: int
class TypeTiers(IntEnum):
"""CT_Type - Type de tiers"""
CLIENT = 0
FOURNISSEUR = 1
SALARIE = 2
AUTRE = 3
class ClientCreateRequest(BaseModel):
intitule: str = Field(
...,
max_length=69,
description="Nom du client (CT_Intitule) - OBLIGATOIRE"
)
numero: Optional[str] = Field(
None,
max_length=17,
description="Numéro client CT_Num (auto si None)"
)
type_tiers: int = Field(
0,
ge=0,
le=3,
description="CT_Type: 0=Client, 1=Fournisseur, 2=Salarié, 3=Autre"
)
qualite: Optional[str] = Field(
"CLI",
max_length=17,
description="CT_Qualite: CLI/FOU/SAL/DIV/AUT"
)
classement: Optional[str] = Field(
None,
max_length=17,
description="CT_Classement"
)
raccourci: Optional[str] = Field(
None,
max_length=7,
description="CT_Raccourci (7 chars max, unique)"
)
siret: Optional[str] = Field(
None,
max_length=15,
description="CT_Siret (14-15 chars)"
)
tva_intra: Optional[str] = Field(
None,
max_length=25,
description="CT_Identifiant (TVA intracommunautaire)"
)
code_naf: Optional[str] = Field(
None,
max_length=7,
description="CT_Ape (Code NAF/APE)"
)
contact: Optional[str] = Field(
None,
max_length=35,
description="CT_Contact (double affectation: client + adresse)"
)
adresse: Optional[str] = Field(
None,
max_length=35,
description="Adresse.Adresse"
)
complement: Optional[str] = Field(
None,
max_length=35,
description="Adresse.Complement"
)
code_postal: Optional[str] = Field(
None,
max_length=9,
description="Adresse.CodePostal"
)
ville: Optional[str] = Field(
None,
max_length=35,
description="Adresse.Ville"
)
region: Optional[str] = Field(
None,
max_length=25,
description="Adresse.CodeRegion"
)
pays: Optional[str] = Field(
None,
max_length=35,
description="Adresse.Pays"
)
telephone: Optional[str] = Field(
None,
max_length=21,
description="Telecom.Telephone"
)
telecopie: Optional[str] = Field(
None,
max_length=21,
description="Telecom.Telecopie (fax)"
)
email: Optional[str] = Field(
None,
max_length=69,
description="Telecom.EMail"
)
site_web: Optional[str] = Field(
None,
max_length=69,
description="Telecom.Site"
)
portable: Optional[str] = Field(
None,
max_length=21,
description="Telecom.Portable"
)
facebook: Optional[str] = Field(
None,
max_length=69,
description="Telecom.Facebook ou CT_Facebook"
)
linkedin: Optional[str] = Field(
None,
max_length=69,
description="Telecom.LinkedIn ou CT_LinkedIn"
)
compte_general: Optional[str] = Field(
None,
max_length=13,
description="CompteGPrinc (défaut selon type_tiers: 4110000, 4010000, 421, 471)"
)
categorie_tarifaire: Optional[str] = Field(
None,
description="N_CatTarif (ID catégorie tarifaire, défaut '0' ou '1')"
)
categorie_comptable: Optional[str] = Field(
None,
description="N_CatCompta (ID catégorie comptable, défaut '0' ou '1')"
)
taux01: Optional[float] = Field(None, description="CT_Taux01")
taux02: Optional[float] = Field(None, description="CT_Taux02")
taux03: Optional[float] = Field(None, description="CT_Taux03")
taux04: Optional[float] = Field(None, description="CT_Taux04")
secteur: Optional[str] = Field(
None,
max_length=21,
description="Alias de statistique01 (CT_Statistique01)"
)
statistique01: Optional[str] = Field(None, max_length=21, description="CT_Statistique01")
statistique02: Optional[str] = Field(None, max_length=21, description="CT_Statistique02")
statistique03: Optional[str] = Field(None, max_length=21, description="CT_Statistique03")
statistique04: Optional[str] = Field(None, max_length=21, description="CT_Statistique04")
statistique05: Optional[str] = Field(None, max_length=21, description="CT_Statistique05")
statistique06: Optional[str] = Field(None, max_length=21, description="CT_Statistique06")
statistique07: Optional[str] = Field(None, max_length=21, description="CT_Statistique07")
statistique08: Optional[str] = Field(None, max_length=21, description="CT_Statistique08")
statistique09: Optional[str] = Field(None, max_length=21, description="CT_Statistique09")
statistique10: Optional[str] = Field(None, max_length=21, description="CT_Statistique10")
encours_autorise: Optional[float] = Field(
None,
description="CT_Encours (montant max autorisé)"
)
assurance_credit: Optional[float] = Field(
None,
description="CT_Assurance (montant assurance crédit)"
)
langue: Optional[int] = Field(
None,
ge=0,
description="CT_Langue (0=Français, 1=Anglais, etc.)"
)
commercial_code: Optional[int] = Field(
None,
description="CO_No (ID du collaborateur commercial)"
)
lettrage_auto: Optional[bool] = Field(
True,
description="CT_Lettrage (1=oui, 0=non)"
)
est_actif: Optional[bool] = Field(
True,
description="Inverse de CT_Sommeil (True=actif, False=en sommeil)"
)
type_facture: Optional[int] = Field(
1,
ge=0,
le=2,
description="CT_Facture: 0=aucune, 1=normale, 2=regroupée"
)
est_prospect: Optional[bool] = Field(
False,
description="CT_Prospect (1=oui, 0=non)"
)
bl_en_facture: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_BLFact (impression BL sur facture)"
)
saut_page: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_Saut (saut de page après impression)"
)
validation_echeance: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_ValidEch"
)
controle_encours: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_ControlEnc"
)
exclure_relance: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_NotRappel"
)
exclure_penalites: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_NotPenal"
)
bon_a_payer: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_BonAPayer"
)
priorite_livraison: Optional[int] = Field(
None,
ge=0,
le=5,
description="CT_PrioriteLivr"
)
livraison_partielle: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_LivrPartielle"
)
delai_transport: Optional[int] = Field(
None,
ge=0,
description="CT_DelaiTransport (jours)"
)
delai_appro: Optional[int] = Field(
None,
ge=0,
description="CT_DelaiAppro (jours)"
)
commentaire: Optional[str] = Field(
None,
max_length=35,
description="CT_Commentaire"
)
section_analytique: Optional[str] = Field(
None,
max_length=13,
description="CA_Num"
)
mode_reglement_code: Optional[int] = Field(
None,
description="MR_No (ID du mode de règlement)"
)
surveillance_active: Optional[int] = Field(
None,
ge=0,
le=1,
description="CT_Surveillance (DOIT être défini AVANT coface)"
)
coface: Optional[str] = Field(
None,
max_length=25,
description="CT_Coface (code Coface)"
)
forme_juridique: Optional[str] = Field(
None,
max_length=33,
description="CT_SvFormeJuri (SARL, SA, etc.)"
)
effectif: Optional[str] = Field(
None,
max_length=11,
description="CT_SvEffectif"
)
sv_regularite: Optional[str] = Field(
None,
max_length=3,
description="CT_SvRegul"
)
sv_cotation: Optional[str] = Field(
None,
max_length=5,
description="CT_SvCotation"
)
sv_objet_maj: Optional[str] = Field(
None,
max_length=61,
description="CT_SvObjetMaj"
)
ca_annuel: Optional[float] = Field(
None,
description="CT_SvCA (Chiffre d'affaires annuel) - alias: sv_chiffre_affaires"
)
sv_chiffre_affaires: Optional[float] = Field(
None,
description="CT_SvCA (alias de ca_annuel)"
)
sv_resultat: Optional[float] = Field(
None,
description="CT_SvResultat"
)
@field_validator('siret')
@classmethod
def validate_siret(cls, v):
"""Valide et nettoie le SIRET"""
if v and v.lower() not in ('none', 'null', ''):
cleaned = v.replace(' ', '').replace('-', '')
if len(cleaned) not in (14, 15):
raise ValueError('Le SIRET doit contenir 14 ou 15 caractères')
return cleaned
return None
@field_validator('email')
@classmethod
def validate_email(cls, v):
"""Valide le format email"""
if v and v.lower() not in ('none', 'null', ''):
v = v.strip()
if '@' not in v:
raise ValueError('Format email invalide')
return v
return None
@field_validator('raccourci')
@classmethod
def validate_raccourci(cls, v):
"""Force le raccourci en majuscules"""
if v and v.lower() not in ('none', 'null', ''):
return v.upper().strip()[:7]
return None
@field_validator(
'adresse', 'code_postal', 'ville', 'pays', 'telephone',
'tva_intra', 'contact', 'complement', mode='before'
)
@classmethod
def clean_none_strings(cls, v):
"""Convertit les chaînes 'None'/'null'/'' en None"""
if isinstance(v, str) and v.lower() in ('none', 'null', ''):
return None
return v
def to_sage_dict(self) -> dict:
"""
Convertit le modèle en dictionnaire compatible avec creer_client()
Mapping 1:1 avec les paramètres réels de la fonction
"""
stat01 = self.statistique01 or self.secteur
ca = self.ca_annuel or self.sv_chiffre_affaires
return {
"intitule": self.intitule,
"numero": self.numero,
"type_tiers": self.type_tiers,
"qualite": self.qualite,
"classement": self.classement,
"raccourci": self.raccourci,
"siret": self.siret,
"tva_intra": self.tva_intra,
"code_naf": self.code_naf,
"contact": self.contact,
"adresse": self.adresse,
"complement": self.complement,
"code_postal": self.code_postal,
"ville": self.ville,
"region": self.region,
"pays": self.pays,
"telephone": self.telephone,
"telecopie": self.telecopie,
"email": self.email,
"site_web": self.site_web,
"portable": self.portable,
"facebook": self.facebook,
"linkedin": self.linkedin,
"compte_general": self.compte_general,
"categorie_tarifaire": self.categorie_tarifaire,
"categorie_comptable": self.categorie_comptable,
"taux01": self.taux01,
"taux02": self.taux02,
"taux03": self.taux03,
"taux04": self.taux04,
"statistique01": stat01,
"statistique02": self.statistique02,
"statistique03": self.statistique03,
"statistique04": self.statistique04,
"statistique05": self.statistique05,
"statistique06": self.statistique06,
"statistique07": self.statistique07,
"statistique08": self.statistique08,
"statistique09": self.statistique09,
"statistique10": self.statistique10,
"secteur": self.secteur, # Gardé pour compatibilité
"encours_autorise": self.encours_autorise,
"assurance_credit": self.assurance_credit,
"langue": self.langue,
"commercial_code": self.commercial_code,
"lettrage_auto": self.lettrage_auto,
"est_actif": self.est_actif,
"type_facture": self.type_facture,
"est_prospect": self.est_prospect,
"bl_en_facture": self.bl_en_facture,
"saut_page": self.saut_page,
"validation_echeance": self.validation_echeance,
"controle_encours": self.controle_encours,
"exclure_relance": self.exclure_relance,
"exclure_penalites": self.exclure_penalites,
"bon_a_payer": self.bon_a_payer,
"priorite_livraison": self.priorite_livraison,
"livraison_partielle": self.livraison_partielle,
"delai_transport": self.delai_transport,
"delai_appro": self.delai_appro,
"commentaire": self.commentaire,
"section_analytique": self.section_analytique,
"mode_reglement_code": self.mode_reglement_code,
"surveillance_active": self.surveillance_active,
"coface": self.coface,
"forme_juridique": self.forme_juridique,
"effectif": self.effectif,
"sv_regularite": self.sv_regularite,
"sv_cotation": self.sv_cotation,
"sv_objet_maj": self.sv_objet_maj,
"ca_annuel": ca,
"sv_chiffre_affaires": self.sv_chiffre_affaires,
"sv_resultat": self.sv_resultat,
}
class Config:
json_schema_extra = {
"example": {
"intitule": "ENTREPRISE EXEMPLE SARL",
"numero": "CLI00123",
"type_tiers": 0,
"qualite": "CLI",
"compte_general": "411000",
"est_prospect": False,
"est_actif": True,
"email": "contact@exemple.fr",
"telephone": "0123456789",
"adresse": "123 Rue de la Paix",
"code_postal": "75001",
"ville": "Paris",
"pays": "France"
}
}
class ClientUpdateGatewayRequest(BaseModel):
"""Modèle pour modification client côté gateway"""
code: str
client_data: Dict
class FournisseurCreateRequest(BaseModel):
intitule: str = Field(..., description="Raison sociale du fournisseur")
compte_collectif: str = Field("401000", description="Compte général rattaché")
num: Optional[str] = Field(None, description="Code fournisseur (auto si vide)")
adresse: Optional[str] = None
code_postal: Optional[str] = None
ville: Optional[str] = None
pays: Optional[str] = None
email: Optional[str] = None
telephone: Optional[str] = None
siret: Optional[str] = None
tva_intra: Optional[str] = None
class FournisseurCreateRequest(BaseModel):
intitule: str = Field(..., description="Raison sociale du fournisseur")
compte_collectif: str = Field("401000", description="Compte général rattaché")
num: Optional[str] = Field(None, description="Code fournisseur (auto si vide)")
adresse: Optional[str] = None
code_postal: Optional[str] = None
ville: Optional[str] = None
pays: Optional[str] = None
email: Optional[str] = None
telephone: Optional[str] = None
siret: Optional[str] = None
tva_intra: Optional[str] = None
class FournisseurUpdateGatewayRequest(BaseModel):
"""Modèle pour modification fournisseur côté gateway"""
code: str
fournisseur_data: Dict
class DevisUpdateGatewayRequest(BaseModel):
"""Modèle pour modification devis côté gateway"""
numero: str
devis_data: Dict
class CommandeCreateRequest(BaseModel):
"""Création d'une commande"""
client_id: str
date_commande: Optional[date] = None
date_livraison: Optional[date] = None
reference: Optional[str] = None
lignes: List[Dict]
class CommandeUpdateGatewayRequest(BaseModel):
"""Modèle pour modification commande côté gateway"""
numero: str
commande_data: Dict
class LivraisonCreateGatewayRequest(BaseModel):
"""Création d'une livraison côté gateway"""
client_id: str
date_livraison: Optional[date] = None
date_livraison_prevue: Optional[date] = None
lignes: List[Dict]
reference: Optional[str] = None
class LivraisonUpdateGatewayRequest(BaseModel):
"""Modèle pour modification livraison côté gateway"""
numero: str
livraison_data: Dict
class AvoirCreateGatewayRequest(BaseModel):
"""Création d'un avoir côté gateway"""
client_id: str
date_avoir: Optional[date] = None
date_livraison: Optional[date] = None
lignes: List[Dict]
reference: Optional[str] = None
class AvoirUpdateGatewayRequest(BaseModel):
"""Modèle pour modification avoir côté gateway"""
numero: str
avoir_data: Dict
class FactureCreateGatewayRequest(BaseModel):
"""Création d'une facture côté gateway"""
client_id: str
date_facture: Optional[date] = None
date_livraison: Optional[date] = None
lignes: List[Dict]
reference: Optional[str] = None
class FactureUpdateGatewayRequest(BaseModel):
"""Modèle pour modification facture côté gateway"""
numero: str
facture_data: Dict
class PDFGenerationRequest(BaseModel):
"""Modèle pour génération PDF"""
doc_id: str = Field(..., description="Numéro du document")
type_doc: int = Field(..., ge=0, le=60, description="Type de document Sage")
class ArticleCreateRequest(BaseModel):
reference: str = Field(..., description="Référence article (max 18 car)")
designation: str = Field(..., description="Désignation (max 69 car)")
famille: Optional[str] = Field(None, description="Code famille")
prix_vente: Optional[float] = Field(None, ge=0, description="Prix vente HT")
prix_achat: Optional[float] = Field(None, ge=0, description="Prix achat HT")
stock_reel: Optional[float] = Field(None, ge=0, description="Stock initial")
stock_mini: Optional[float] = Field(None, ge=0, description="Stock minimum")
code_ean: Optional[str] = Field(None, description="Code-barres EAN")
unite_vente: Optional[str] = Field("UN", description="Unité de vente")
tva_code: Optional[str] = Field(None, description="Code TVA")
description: Optional[str] = Field(None, description="Description/Commentaire")
class ArticleUpdateGatewayRequest(BaseModel):
"""Modèle pour modification article côté gateway"""
reference: str
article_data: Dict
class MouvementStockLigneRequest(BaseModel):
article_ref: str = Field(..., description="Référence de l'article")
quantite: float = Field(..., gt=0, description="Quantité (>0)")
depot_code: Optional[str] = Field(None, description="Code du dépôt (ex: '01')")
prix_unitaire: Optional[float] = Field(
None, ge=0, description="Prix unitaire (optionnel)"
)
commentaire: Optional[str] = Field(None, description="Commentaire ligne")
numero_lot: Optional[str] = Field(
None, description="Numéro de lot (pour FIFO/LIFO)"
)
stock_mini: Optional[float] = Field(
None,
ge=0,
description="""Stock minimum à définir pour cet article.
Si fourni, met à jour AS_QteMini dans F_ARTSTOCK.
Laisser None pour ne pas modifier.""",
)
stock_maxi: Optional[float] = Field(
None,
ge=0,
description="""Stock maximum à définir pour cet article.
Doit être > stock_mini si les deux sont fournis.""",
)
class Config:
schema_extra = {
"example": {
"article_ref": "ARTS-001",
"quantite": 50.0,
"depot_code": "01",
"prix_unitaire": 100.0,
"commentaire": "Réapprovisionnement",
"numero_lot": "LOT20241217",
"stock_mini": 10.0,
"stock_maxi": 200.0,
}
}
@validator("stock_maxi")
def validate_stock_maxi(cls, v, values):
"""Valide que stock_maxi > stock_mini si les deux sont fournis"""
if (
v is not None
and "stock_mini" in values
and values["stock_mini"] is not None
):
if v <= values["stock_mini"]:
raise ValueError(
"stock_maxi doit être strictement supérieur à stock_mini"
)
return v
class EntreeStockRequest(BaseModel):
"""Création d'un bon d'entrée en stock"""
date_entree: Optional[date] = Field(
None, description="Date du mouvement (aujourd'hui par défaut)"
)
reference: Optional[str] = Field(None, description="Référence externe")
depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)"
)
lignes: List[MouvementStockLigneRequest] = Field(
..., min_items=1, description="Lignes du mouvement"
)
commentaire: Optional[str] = Field(None, description="Commentaire général")
class SortieStockRequest(BaseModel):
"""Création d'un bon de sortie de stock"""
date_sortie: Optional[date] = Field(
None, description="Date du mouvement (aujourd'hui par défaut)"
)
reference: Optional[str] = Field(None, description="Référence externe")
depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)"
)
lignes: List[MouvementStockLigneRequest] = Field(
..., min_items=1, description="Lignes du mouvement"
)
commentaire: Optional[str] = Field(None, description="Commentaire général")
class FamilleCreate(BaseModel):
"""Modèle pour créer une famille d'articles"""
code: str = Field(..., description="Code famille (max 18 car)", max_length=18)
intitule: str = Field(..., description="Intitulé (max 69 car)", max_length=69)
type: int = Field(0, description="0=Détail, 1=Total")
compte_achat: Optional[str] = Field(
None, description="Compte général d'achat (ex: 607000)"
)
compte_vente: Optional[str] = Field(
None, description="Compte général de vente (ex: 707000)"
)
class ContactCreateRequest(BaseModel):
"""Requête de création de contact"""
numero: str
civilite: Optional[str] = None
nom: str
prenom: Optional[str] = None
fonction: Optional[str] = None
service_code: Optional[int] = None
telephone: Optional[str] = None
portable: Optional[str] = None
telecopie: Optional[str] = None
email: Optional[str] = None
facebook: Optional[str] = None
linkedin: Optional[str] = None
skype: Optional[str] = None
class ContactListRequest(BaseModel):
"""Requête de liste des contacts"""
numero: str
class ContactGetRequest(BaseModel):
"""Requête de récupération d'un contact"""
numero: str
contact_numero: int
class ContactUpdateRequest(BaseModel):
"""Requête de modification d'un contact"""
numero: str
contact_numero: int
updates: Dict
class ContactDeleteRequest(BaseModel):
"""Requête de suppression d'un contact"""
numero: str
contact_numero: int
def verify_token(x_sage_token: str = Header(...)): def verify_token(x_sage_token: str = Header(...)):
"""Vérification du token d'authentification""" """Vérification du token d'authentification"""
if x_sage_token != settings.sage_gateway_token: if x_sage_token != settings.sage_gateway_token:
@ -1171,9 +331,7 @@ def transformer_document(
} }
if (type_source, type_cible) not in transformations_valides: if (type_source, type_cible) not in transformations_valides:
logger.error( logger.error(f" Transformation non autorisée: {type_source}{type_cible}")
f" Transformation non autorisée: {type_source}{type_cible}"
)
raise HTTPException( raise HTTPException(
400, 400,
f"Transformation non autorisée: type {type_source} → type {type_cible}. " f"Transformation non autorisée: type {type_source} → type {type_cible}. "
@ -1389,7 +547,6 @@ def create_fournisseur_endpoint(req: FournisseurCreateRequest):
@app.post("/sage/fournisseurs/update", dependencies=[Depends(verify_token)]) @app.post("/sage/fournisseurs/update", dependencies=[Depends(verify_token)])
def modifier_fournisseur_endpoint(req: FournisseurUpdateGatewayRequest): def modifier_fournisseur_endpoint(req: FournisseurUpdateGatewayRequest):
try: try:
resultat = sage.modifier_fournisseur(req.code, req.fournisseur_data) resultat = sage.modifier_fournisseur(req.code, req.fournisseur_data)
return {"success": True, "data": resultat} return {"success": True, "data": resultat}
@ -1527,8 +684,6 @@ def modifier_devis_endpoint(req: DevisUpdateGatewayRequest):
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@app.post("/sage/commandes/create", dependencies=[Depends(verify_token)]) @app.post("/sage/commandes/create", dependencies=[Depends(verify_token)])
def creer_commande_endpoint(req: CommandeCreateRequest): def creer_commande_endpoint(req: CommandeCreateRequest):
try: try:
@ -1741,8 +896,6 @@ async def creer_famille(famille: FamilleCreate):
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}") raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
@app.get( @app.get(
"/sage/familles", "/sage/familles",
response_model=dict, response_model=dict,
@ -1767,8 +920,6 @@ async def lister_familles(filtre: str = ""):
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}") raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
@app.get( @app.get(
"/sage/familles/{code}", "/sage/familles/{code}",
response_model=dict, response_model=dict,
@ -1791,8 +942,6 @@ async def lire_famille(code: str):
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}") raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
@app.get("/sage/familles/stats", response_model=dict) @app.get("/sage/familles/stats", response_model=dict)
async def stats_familles(): async def stats_familles():
try: try:
@ -2165,10 +1314,7 @@ def contacts_set_default(req: ContactGetRequest):
def tiers_list(req: TiersListRequest): def tiers_list(req: TiersListRequest):
"""Liste des tiers avec filtres optionnels""" """Liste des tiers avec filtres optionnels"""
try: try:
tiers = sage.lister_tous_tiers( tiers = sage.lister_tous_tiers(type_tiers=req.type_tiers, filtre=req.filtre)
type_tiers=req.type_tiers,
filtre=req.filtre
)
return {"success": True, "data": tiers} return {"success": True, "data": tiers}
except Exception as e: except Exception as e:
logger.error(f" Erreur liste tiers: {e}") logger.error(f" Erreur liste tiers: {e}")

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,107 @@
from schemas.tiers.tiers import (TiersListRequest,) from schemas.tiers.tiers import (
TiersListRequest,
TypeTiers
)
from schemas.tiers.contact import (
ContactCreateRequest,
ContactDeleteRequest,
ContactGetRequest,
ContactListRequest,
ContactUpdateRequest,
)
from schemas.tiers.clients import (
ClientCreateRequest,
ClientUpdateGatewayRequest
)
from schemas.others.general_schema import (
FiltreRequest,
ChampLibreRequest,
CodeRequest,
StatutRequest
)
from schemas.documents.documents import (
TransformationRequest,
TypeDocument,
DocumentGetRequest,
PDFGenerationRequest
)
from schemas.documents.devis import (
DevisRequest,
DevisUpdateGatewayRequest
)
from schemas.tiers.fournisseurs import (
FournisseurCreateRequest,
FournisseurUpdateGatewayRequest
)
from schemas.documents.avoirs import (
AvoirCreateGatewayRequest,
AvoirUpdateGatewayRequest
)
from schemas.documents.commandes import (
CommandeCreateRequest,
CommandeUpdateGatewayRequest
)
from schemas.documents.factures import (
FactureCreateGatewayRequest,
FactureUpdateGatewayRequest
)
from schemas.documents.livraisons import (
LivraisonCreateGatewayRequest,
LivraisonUpdateGatewayRequest
)
from schemas.articles.articles import (
ArticleCreateRequest,
ArticleUpdateGatewayRequest,
MouvementStockLigneRequest,
EntreeStockRequest,
SortieStockRequest
)
from schemas.articles.famille_d_articles import FamilleCreate
__all__ = [ __all__ = [
"TiersListRequest", "TiersListRequest",
] "ContactCreateRequest",
"ContactDeleteRequest",
"ContactGetRequest",
"ContactListRequest",
"ContactUpdateRequest",
"ClientCreateRequest",
"ClientUpdateGatewayRequest",
"FiltreRequest",
"ChampLibreRequest",
"CodeRequest",
"TransformationRequest",
"TypeDocument",
"DevisRequest",
"DocumentGetRequest",
"StatutRequest",
"TypeTiers",
"DevisUpdateGatewayRequest",
"FournisseurCreateRequest",
"FournisseurUpdateGatewayRequest",
"AvoirCreateGatewayRequest",
"AvoirUpdateGatewayRequest",
"CommandeCreateRequest",
"CommandeUpdateGatewayRequest",
"FactureCreateGatewayRequest",
"FactureUpdateGatewayRequest",
"LivraisonCreateGatewayRequest",
"LivraisonUpdateGatewayRequest",
"ArticleCreateRequest",
"ArticleUpdateGatewayRequest",
"MouvementStockLigneRequest",
"EntreeStockRequest",
"SortieStockRequest",
"FamilleCreate",
"PDFGenerationRequest"
]

View file

@ -0,0 +1,111 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class ArticleCreateRequest(BaseModel):
reference: str = Field(..., description="Référence article (max 18 car)")
designation: str = Field(..., description="Désignation (max 69 car)")
famille: Optional[str] = Field(None, description="Code famille")
prix_vente: Optional[float] = Field(None, ge=0, description="Prix vente HT")
prix_achat: Optional[float] = Field(None, ge=0, description="Prix achat HT")
stock_reel: Optional[float] = Field(None, ge=0, description="Stock initial")
stock_mini: Optional[float] = Field(None, ge=0, description="Stock minimum")
code_ean: Optional[str] = Field(None, description="Code-barres EAN")
unite_vente: Optional[str] = Field("UN", description="Unité de vente")
tva_code: Optional[str] = Field(None, description="Code TVA")
description: Optional[str] = Field(None, description="Description/Commentaire")
class ArticleUpdateGatewayRequest(BaseModel):
"""Modèle pour modification article côté gateway"""
reference: str
article_data: Dict
class MouvementStockLigneRequest(BaseModel):
article_ref: str = Field(..., description="Référence de l'article")
quantite: float = Field(..., gt=0, description="Quantité (>0)")
depot_code: Optional[str] = Field(None, description="Code du dépôt (ex: '01')")
prix_unitaire: Optional[float] = Field(
None, ge=0, description="Prix unitaire (optionnel)"
)
commentaire: Optional[str] = Field(None, description="Commentaire ligne")
numero_lot: Optional[str] = Field(
None, description="Numéro de lot (pour FIFO/LIFO)"
)
stock_mini: Optional[float] = Field(
None,
ge=0,
description="""Stock minimum à définir pour cet article.
Si fourni, met à jour AS_QteMini dans F_ARTSTOCK.
Laisser None pour ne pas modifier.""",
)
stock_maxi: Optional[float] = Field(
None,
ge=0,
description="""Stock maximum à définir pour cet article.
Doit être > stock_mini si les deux sont fournis.""",
)
class Config:
schema_extra = {
"example": {
"article_ref": "ARTS-001",
"quantite": 50.0,
"depot_code": "01",
"prix_unitaire": 100.0,
"commentaire": "Réapprovisionnement",
"numero_lot": "LOT20241217",
"stock_mini": 10.0,
"stock_maxi": 200.0,
}
}
@validator("stock_maxi")
def validate_stock_maxi(cls, v, values):
"""Valide que stock_maxi > stock_mini si les deux sont fournis"""
if (
v is not None
and "stock_mini" in values
and values["stock_mini"] is not None
):
if v <= values["stock_mini"]:
raise ValueError(
"stock_maxi doit être strictement supérieur à stock_mini"
)
return v
class EntreeStockRequest(BaseModel):
"""Création d'un bon d'entrée en stock"""
date_entree: Optional[date] = Field(
None, description="Date du mouvement (aujourd'hui par défaut)"
)
reference: Optional[str] = Field(None, description="Référence externe")
depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)"
)
lignes: List[MouvementStockLigneRequest] = Field(
..., min_items=1, description="Lignes du mouvement"
)
commentaire: Optional[str] = Field(None, description="Commentaire général")
class SortieStockRequest(BaseModel):
"""Création d'un bon de sortie de stock"""
date_sortie: Optional[date] = Field(
None, description="Date du mouvement (aujourd'hui par défaut)"
)
reference: Optional[str] = Field(None, description="Référence externe")
depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)"
)
lignes: List[MouvementStockLigneRequest] = Field(
..., min_items=1, description="Lignes du mouvement"
)
commentaire: Optional[str] = Field(None, description="Commentaire général")

View file

@ -0,0 +1,19 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class FamilleCreate(BaseModel):
"""Modèle pour créer une famille d'articles"""
code: str = Field(..., description="Code famille (max 18 car)", max_length=18)
intitule: str = Field(..., description="Intitulé (max 69 car)", max_length=69)
type: int = Field(0, description="0=Détail, 1=Total")
compte_achat: Optional[str] = Field(
None, description="Compte général d'achat (ex: 607000)"
)
compte_vente: Optional[str] = Field(
None, description="Compte général de vente (ex: 707000)"
)

View file

@ -0,0 +1,22 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class AvoirCreateGatewayRequest(BaseModel):
"""Création d'un avoir côté gateway"""
client_id: str
date_avoir: Optional[date] = None
date_livraison: Optional[date] = None
lignes: List[Dict]
reference: Optional[str] = None
class AvoirUpdateGatewayRequest(BaseModel):
"""Modèle pour modification avoir côté gateway"""
numero: str
avoir_data: Dict

View file

@ -0,0 +1,21 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class CommandeCreateRequest(BaseModel):
"""Création d'une commande"""
client_id: str
date_commande: Optional[date] = None
date_livraison: Optional[date] = None
reference: Optional[str] = None
lignes: List[Dict]
class CommandeUpdateGatewayRequest(BaseModel):
"""Modèle pour modification commande côté gateway"""
numero: str
commande_data: Dict

View file

@ -0,0 +1,19 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class DevisRequest(BaseModel):
client_id: str
date_devis: Optional[date] = None
date_livraison: Optional[date] = None
reference: Optional[str] = None
lignes: List[Dict]
class DevisUpdateGatewayRequest(BaseModel):
"""Modèle pour modification devis côté gateway"""
numero: str
devis_data: Dict

View file

@ -0,0 +1,32 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class TypeDocument(int, Enum):
DEVIS = 0
BON_LIVRAISON = 1
BON_RETOUR = 2
COMMANDE = 3
PREPARATION = 4
FACTURE = 5
class DocumentGetRequest(BaseModel):
numero: str
type_doc: int
class TransformationRequest(BaseModel):
numero_source: str
type_source: int
type_cible: int
class PDFGenerationRequest(BaseModel):
"""Modèle pour génération PDF"""
doc_id: str = Field(..., description="Numéro du document")
type_doc: int = Field(..., ge=0, le=60, description="Type de document Sage")

View file

@ -0,0 +1,22 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class FactureCreateGatewayRequest(BaseModel):
"""Création d'une facture côté gateway"""
client_id: str
date_facture: Optional[date] = None
date_livraison: Optional[date] = None
lignes: List[Dict]
reference: Optional[str] = None
class FactureUpdateGatewayRequest(BaseModel):
"""Modèle pour modification facture côté gateway"""
numero: str
facture_data: Dict

View file

@ -0,0 +1,21 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class LivraisonCreateGatewayRequest(BaseModel):
"""Création d'une livraison côté gateway"""
client_id: str
date_livraison: Optional[date] = None
date_livraison_prevue: Optional[date] = None
lignes: List[Dict]
reference: Optional[str] = None
class LivraisonUpdateGatewayRequest(BaseModel):
"""Modèle pour modification livraison côté gateway"""
numero: str
livraison_data: Dict

View file

@ -0,0 +1,21 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
class FiltreRequest(BaseModel):
filtre: Optional[str] = ""
class CodeRequest(BaseModel):
code: str
class ChampLibreRequest(BaseModel):
doc_id: str
type_doc: int
nom_champ: str
valeur: str
class StatutRequest(BaseModel):
nouveau_statut: int

410
schemas/tiers/clients.py Normal file
View file

@ -0,0 +1,410 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
class ClientCreateRequest(BaseModel):
intitule: str = Field(
..., max_length=69, description="Nom du client (CT_Intitule) - OBLIGATOIRE"
)
numero: Optional[str] = Field(
None, max_length=17, description="Numéro client CT_Num (auto si None)"
)
type_tiers: int = Field(
0,
ge=0,
le=3,
description="CT_Type: 0=Client, 1=Fournisseur, 2=Salarié, 3=Autre",
)
qualite: Optional[str] = Field(
"CLI", max_length=17, description="CT_Qualite: CLI/FOU/SAL/DIV/AUT"
)
classement: Optional[str] = Field(None, max_length=17, description="CT_Classement")
raccourci: Optional[str] = Field(
None, max_length=7, description="CT_Raccourci (7 chars max, unique)"
)
siret: Optional[str] = Field(
None, max_length=15, description="CT_Siret (14-15 chars)"
)
tva_intra: Optional[str] = Field(
None, max_length=25, description="CT_Identifiant (TVA intracommunautaire)"
)
code_naf: Optional[str] = Field(
None, max_length=7, description="CT_Ape (Code NAF/APE)"
)
contact: Optional[str] = Field(
None,
max_length=35,
description="CT_Contact (double affectation: client + adresse)",
)
adresse: Optional[str] = Field(None, max_length=35, description="Adresse.Adresse")
complement: Optional[str] = Field(
None, max_length=35, description="Adresse.Complement"
)
code_postal: Optional[str] = Field(
None, max_length=9, description="Adresse.CodePostal"
)
ville: Optional[str] = Field(None, max_length=35, description="Adresse.Ville")
region: Optional[str] = Field(None, max_length=25, description="Adresse.CodeRegion")
pays: Optional[str] = Field(None, max_length=35, description="Adresse.Pays")
telephone: Optional[str] = Field(
None, max_length=21, description="Telecom.Telephone"
)
telecopie: Optional[str] = Field(
None, max_length=21, description="Telecom.Telecopie (fax)"
)
email: Optional[str] = Field(None, max_length=69, description="Telecom.EMail")
site_web: Optional[str] = Field(None, max_length=69, description="Telecom.Site")
portable: Optional[str] = Field(None, max_length=21, description="Telecom.Portable")
facebook: Optional[str] = Field(
None, max_length=69, description="Telecom.Facebook ou CT_Facebook"
)
linkedin: Optional[str] = Field(
None, max_length=69, description="Telecom.LinkedIn ou CT_LinkedIn"
)
compte_general: Optional[str] = Field(
None,
max_length=13,
description="CompteGPrinc (défaut selon type_tiers: 4110000, 4010000, 421, 471)",
)
categorie_tarifaire: Optional[str] = Field(
None, description="N_CatTarif (ID catégorie tarifaire, défaut '0' ou '1')"
)
categorie_comptable: Optional[str] = Field(
None, description="N_CatCompta (ID catégorie comptable, défaut '0' ou '1')"
)
taux01: Optional[float] = Field(None, description="CT_Taux01")
taux02: Optional[float] = Field(None, description="CT_Taux02")
taux03: Optional[float] = Field(None, description="CT_Taux03")
taux04: Optional[float] = Field(None, description="CT_Taux04")
secteur: Optional[str] = Field(
None, max_length=21, description="Alias de statistique01 (CT_Statistique01)"
)
statistique01: Optional[str] = Field(
None, max_length=21, description="CT_Statistique01"
)
statistique02: Optional[str] = Field(
None, max_length=21, description="CT_Statistique02"
)
statistique03: Optional[str] = Field(
None, max_length=21, description="CT_Statistique03"
)
statistique04: Optional[str] = Field(
None, max_length=21, description="CT_Statistique04"
)
statistique05: Optional[str] = Field(
None, max_length=21, description="CT_Statistique05"
)
statistique06: Optional[str] = Field(
None, max_length=21, description="CT_Statistique06"
)
statistique07: Optional[str] = Field(
None, max_length=21, description="CT_Statistique07"
)
statistique08: Optional[str] = Field(
None, max_length=21, description="CT_Statistique08"
)
statistique09: Optional[str] = Field(
None, max_length=21, description="CT_Statistique09"
)
statistique10: Optional[str] = Field(
None, max_length=21, description="CT_Statistique10"
)
encours_autorise: Optional[float] = Field(
None, description="CT_Encours (montant max autorisé)"
)
assurance_credit: Optional[float] = Field(
None, description="CT_Assurance (montant assurance crédit)"
)
langue: Optional[int] = Field(
None, ge=0, description="CT_Langue (0=Français, 1=Anglais, etc.)"
)
commercial_code: Optional[int] = Field(
None, description="CO_No (ID du collaborateur commercial)"
)
lettrage_auto: Optional[bool] = Field(
True, description="CT_Lettrage (1=oui, 0=non)"
)
est_actif: Optional[bool] = Field(
True, description="Inverse de CT_Sommeil (True=actif, False=en sommeil)"
)
type_facture: Optional[int] = Field(
1, ge=0, le=2, description="CT_Facture: 0=aucune, 1=normale, 2=regroupée"
)
est_prospect: Optional[bool] = Field(
False, description="CT_Prospect (1=oui, 0=non)"
)
bl_en_facture: Optional[int] = Field(
None, ge=0, le=1, description="CT_BLFact (impression BL sur facture)"
)
saut_page: Optional[int] = Field(
None, ge=0, le=1, description="CT_Saut (saut de page après impression)"
)
validation_echeance: Optional[int] = Field(
None, ge=0, le=1, description="CT_ValidEch"
)
controle_encours: Optional[int] = Field(
None, ge=0, le=1, description="CT_ControlEnc"
)
exclure_relance: Optional[int] = Field(None, ge=0, le=1, description="CT_NotRappel")
exclure_penalites: Optional[int] = Field(
None, ge=0, le=1, description="CT_NotPenal"
)
bon_a_payer: Optional[int] = Field(None, ge=0, le=1, description="CT_BonAPayer")
priorite_livraison: Optional[int] = Field(
None, ge=0, le=5, description="CT_PrioriteLivr"
)
livraison_partielle: Optional[int] = Field(
None, ge=0, le=1, description="CT_LivrPartielle"
)
delai_transport: Optional[int] = Field(
None, ge=0, description="CT_DelaiTransport (jours)"
)
delai_appro: Optional[int] = Field(None, ge=0, description="CT_DelaiAppro (jours)")
commentaire: Optional[str] = Field(
None, max_length=35, description="CT_Commentaire"
)
section_analytique: Optional[str] = Field(None, max_length=13, description="CA_Num")
mode_reglement_code: Optional[int] = Field(
None, description="MR_No (ID du mode de règlement)"
)
surveillance_active: Optional[int] = Field(
None, ge=0, le=1, description="CT_Surveillance (DOIT être défini AVANT coface)"
)
coface: Optional[str] = Field(
None, max_length=25, description="CT_Coface (code Coface)"
)
forme_juridique: Optional[str] = Field(
None, max_length=33, description="CT_SvFormeJuri (SARL, SA, etc.)"
)
effectif: Optional[str] = Field(None, max_length=11, description="CT_SvEffectif")
sv_regularite: Optional[str] = Field(None, max_length=3, description="CT_SvRegul")
sv_cotation: Optional[str] = Field(None, max_length=5, description="CT_SvCotation")
sv_objet_maj: Optional[str] = Field(
None, max_length=61, description="CT_SvObjetMaj"
)
ca_annuel: Optional[float] = Field(
None,
description="CT_SvCA (Chiffre d'affaires annuel) - alias: sv_chiffre_affaires",
)
sv_chiffre_affaires: Optional[float] = Field(
None, description="CT_SvCA (alias de ca_annuel)"
)
sv_resultat: Optional[float] = Field(None, description="CT_SvResultat")
@field_validator("siret")
@classmethod
def validate_siret(cls, v):
"""Valide et nettoie le SIRET"""
if v and v.lower() not in ("none", "null", ""):
cleaned = v.replace(" ", "").replace("-", "")
if len(cleaned) not in (14, 15):
raise ValueError("Le SIRET doit contenir 14 ou 15 caractères")
return cleaned
return None
@field_validator("email")
@classmethod
def validate_email(cls, v):
"""Valide le format email"""
if v and v.lower() not in ("none", "null", ""):
v = v.strip()
if "@" not in v:
raise ValueError("Format email invalide")
return v
return None
@field_validator("raccourci")
@classmethod
def validate_raccourci(cls, v):
"""Force le raccourci en majuscules"""
if v and v.lower() not in ("none", "null", ""):
return v.upper().strip()[:7]
return None
@field_validator(
"adresse",
"code_postal",
"ville",
"pays",
"telephone",
"tva_intra",
"contact",
"complement",
mode="before",
)
@classmethod
def clean_none_strings(cls, v):
"""Convertit les chaînes 'None'/'null'/'' en None"""
if isinstance(v, str) and v.lower() in ("none", "null", ""):
return None
return v
def to_sage_dict(self) -> dict:
"""
Convertit le modèle en dictionnaire compatible avec creer_client()
Mapping 1:1 avec les paramètres réels de la fonction
"""
stat01 = self.statistique01 or self.secteur
ca = self.ca_annuel or self.sv_chiffre_affaires
return {
"intitule": self.intitule,
"numero": self.numero,
"type_tiers": self.type_tiers,
"qualite": self.qualite,
"classement": self.classement,
"raccourci": self.raccourci,
"siret": self.siret,
"tva_intra": self.tva_intra,
"code_naf": self.code_naf,
"contact": self.contact,
"adresse": self.adresse,
"complement": self.complement,
"code_postal": self.code_postal,
"ville": self.ville,
"region": self.region,
"pays": self.pays,
"telephone": self.telephone,
"telecopie": self.telecopie,
"email": self.email,
"site_web": self.site_web,
"portable": self.portable,
"facebook": self.facebook,
"linkedin": self.linkedin,
"compte_general": self.compte_general,
"categorie_tarifaire": self.categorie_tarifaire,
"categorie_comptable": self.categorie_comptable,
"taux01": self.taux01,
"taux02": self.taux02,
"taux03": self.taux03,
"taux04": self.taux04,
"statistique01": stat01,
"statistique02": self.statistique02,
"statistique03": self.statistique03,
"statistique04": self.statistique04,
"statistique05": self.statistique05,
"statistique06": self.statistique06,
"statistique07": self.statistique07,
"statistique08": self.statistique08,
"statistique09": self.statistique09,
"statistique10": self.statistique10,
"secteur": self.secteur, # Gardé pour compatibilité
"encours_autorise": self.encours_autorise,
"assurance_credit": self.assurance_credit,
"langue": self.langue,
"commercial_code": self.commercial_code,
"lettrage_auto": self.lettrage_auto,
"est_actif": self.est_actif,
"type_facture": self.type_facture,
"est_prospect": self.est_prospect,
"bl_en_facture": self.bl_en_facture,
"saut_page": self.saut_page,
"validation_echeance": self.validation_echeance,
"controle_encours": self.controle_encours,
"exclure_relance": self.exclure_relance,
"exclure_penalites": self.exclure_penalites,
"bon_a_payer": self.bon_a_payer,
"priorite_livraison": self.priorite_livraison,
"livraison_partielle": self.livraison_partielle,
"delai_transport": self.delai_transport,
"delai_appro": self.delai_appro,
"commentaire": self.commentaire,
"section_analytique": self.section_analytique,
"mode_reglement_code": self.mode_reglement_code,
"surveillance_active": self.surveillance_active,
"coface": self.coface,
"forme_juridique": self.forme_juridique,
"effectif": self.effectif,
"sv_regularite": self.sv_regularite,
"sv_cotation": self.sv_cotation,
"sv_objet_maj": self.sv_objet_maj,
"ca_annuel": ca,
"sv_chiffre_affaires": self.sv_chiffre_affaires,
"sv_resultat": self.sv_resultat,
}
class Config:
json_schema_extra = {
"example": {
"intitule": "ENTREPRISE EXEMPLE SARL",
"numero": "CLI00123",
"type_tiers": 0,
"qualite": "CLI",
"compte_general": "411000",
"est_prospect": False,
"est_actif": True,
"email": "contact@exemple.fr",
"telephone": "0123456789",
"adresse": "123 Rue de la Paix",
"code_postal": "75001",
"ville": "Paris",
"pays": "France",
}
}
class ClientUpdateGatewayRequest(BaseModel):
"""Modèle pour modification client côté gateway"""
code: str
client_data: Dict

43
schemas/tiers/contact.py Normal file
View file

@ -0,0 +1,43 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
class ContactCreateRequest(BaseModel):
"""Requête de création de contact"""
numero: str
civilite: Optional[str] = None
nom: str
prenom: Optional[str] = None
fonction: Optional[str] = None
service_code: Optional[int] = None
telephone: Optional[str] = None
portable: Optional[str] = None
telecopie: Optional[str] = None
email: Optional[str] = None
facebook: Optional[str] = None
linkedin: Optional[str] = None
skype: Optional[str] = None
class ContactListRequest(BaseModel):
"""Requête de liste des contacts"""
numero: str
class ContactGetRequest(BaseModel):
"""Requête de récupération d'un contact"""
numero: str
contact_numero: int
class ContactUpdateRequest(BaseModel):
"""Requête de modification d'un contact"""
numero: str
contact_numero: int
updates: Dict
class ContactDeleteRequest(BaseModel):
"""Requête de suppression d'un contact"""
numero: str
contact_numero: int

View file

@ -0,0 +1,41 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict
from enum import Enum, IntEnum
from datetime import datetime, date
class FournisseurCreateRequest(BaseModel):
intitule: str = Field(..., description="Raison sociale du fournisseur")
compte_collectif: str = Field("401000", description="Compte général rattaché")
num: Optional[str] = Field(None, description="Code fournisseur (auto si vide)")
adresse: Optional[str] = None
code_postal: Optional[str] = None
ville: Optional[str] = None
pays: Optional[str] = None
email: Optional[str] = None
telephone: Optional[str] = None
siret: Optional[str] = None
tva_intra: Optional[str] = None
class FournisseurCreateRequest(BaseModel):
intitule: str = Field(..., description="Raison sociale du fournisseur")
compte_collectif: str = Field("401000", description="Compte général rattaché")
num: Optional[str] = Field(None, description="Code fournisseur (auto si vide)")
adresse: Optional[str] = None
code_postal: Optional[str] = None
ville: Optional[str] = None
pays: Optional[str] = None
email: Optional[str] = None
telephone: Optional[str] = None
siret: Optional[str] = None
tva_intra: Optional[str] = None
class FournisseurUpdateGatewayRequest(BaseModel):
"""Modèle pour modification fournisseur côté gateway"""
code: str
fournisseur_data: Dict

View file

@ -1,5 +1,6 @@
from pydantic import BaseModel, Field, validator, EmailStr, field_validator from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict from typing import Optional, List, Dict
from enum import Enum, IntEnum
class TiersListRequest(BaseModel): class TiersListRequest(BaseModel):
"""Requête de listage des tiers""" """Requête de listage des tiers"""
@ -11,3 +12,11 @@ class TiersListRequest(BaseModel):
"", "",
description="Filtre sur code ou intitulé" description="Filtre sur code ou intitulé"
) )
class TypeTiers(IntEnum):
"""CT_Type - Type de tiers"""
CLIENT = 0
FOURNISSEUR = 1
SALARIE = 2
AUTRE = 3