829 lines
30 KiB
Python
829 lines
30 KiB
Python
from pydantic import BaseModel, Field, field_validator
|
|
from typing import List, Optional
|
|
from schemas.tiers.contact import Contact
|
|
|
|
class ClientResponse(BaseModel):
|
|
"""Modèle de réponse client simplifié (pour listes)"""
|
|
|
|
numero: Optional[str] = None
|
|
intitule: Optional[str] = None
|
|
adresse: Optional[str] = None
|
|
code_postal: Optional[str] = None
|
|
ville: Optional[str] = None
|
|
email: Optional[str] = None
|
|
telephone: Optional[str] = None
|
|
|
|
|
|
class ClientDetails(BaseModel):
|
|
numero: Optional[str] = Field(None, description="Code client (CT_Num)")
|
|
intitule: Optional[str] = Field(
|
|
None, description="Raison sociale ou Nom complet (CT_Intitule)"
|
|
)
|
|
type_tiers: Optional[int] = Field(
|
|
None, description="Type : 0=Client, 1=Fournisseur (CT_Type)"
|
|
)
|
|
qualite: Optional[str] = Field(
|
|
None, description="Qualité Sage : CLI, FOU, PRO (CT_Qualite)"
|
|
)
|
|
classement: Optional[str] = Field(
|
|
None, description="Code de classement (CT_Classement)"
|
|
)
|
|
raccourci: Optional[str] = Field(
|
|
None, description="Code raccourci 7 car. (CT_Raccourci)"
|
|
)
|
|
siret: Optional[str] = Field(None, description="N° SIRET 14 chiffres (CT_Siret)")
|
|
tva_intra: Optional[str] = Field(
|
|
None, description="N° TVA intracommunautaire (CT_Identifiant)"
|
|
)
|
|
code_naf: Optional[str] = Field(None, description="Code NAF/APE (CT_Ape)")
|
|
|
|
contact: Optional[str] = Field(
|
|
None, description="Nom du contact principal (CT_Contact)"
|
|
)
|
|
adresse: Optional[str] = Field(None, description="Adresse ligne 1 (CT_Adresse)")
|
|
complement: Optional[str] = Field(
|
|
None, description="Complément d'adresse (CT_Complement)"
|
|
)
|
|
code_postal: Optional[str] = Field(None, description="Code postal (CT_CodePostal)")
|
|
ville: Optional[str] = Field(None, description="Ville (CT_Ville)")
|
|
region: Optional[str] = Field(None, description="Région/État (CT_CodeRegion)")
|
|
pays: Optional[str] = Field(None, description="Pays (CT_Pays)")
|
|
|
|
telephone: Optional[str] = Field(None, description="Téléphone fixe (CT_Telephone)")
|
|
telecopie: Optional[str] = Field(None, description="Fax (CT_Telecopie)")
|
|
email: Optional[str] = Field(None, description="Email principal (CT_EMail)")
|
|
site_web: Optional[str] = Field(None, description="Site web (CT_Site)")
|
|
facebook: Optional[str] = Field(None, description="Profil Facebook (CT_Facebook)")
|
|
linkedin: Optional[str] = Field(None, description="Profil LinkedIn (CT_LinkedIn)")
|
|
|
|
taux01: Optional[float] = Field(None, description="Taux personnalisé 1 (CT_Taux01)")
|
|
taux02: Optional[float] = Field(None, description="Taux personnalisé 2 (CT_Taux02)")
|
|
taux03: Optional[float] = Field(None, description="Taux personnalisé 3 (CT_Taux03)")
|
|
taux04: Optional[float] = Field(None, description="Taux personnalisé 4 (CT_Taux04)")
|
|
|
|
statistique01: Optional[str] = Field(
|
|
None, description="Statistique 1 (CT_Statistique01)"
|
|
)
|
|
statistique02: Optional[str] = Field(
|
|
None, description="Statistique 2 (CT_Statistique02)"
|
|
)
|
|
statistique03: Optional[str] = Field(
|
|
None, description="Statistique 3 (CT_Statistique03)"
|
|
)
|
|
statistique04: Optional[str] = Field(
|
|
None, description="Statistique 4 (CT_Statistique04)"
|
|
)
|
|
statistique05: Optional[str] = Field(
|
|
None, description="Statistique 5 (CT_Statistique05)"
|
|
)
|
|
statistique06: Optional[str] = Field(
|
|
None, description="Statistique 6 (CT_Statistique06)"
|
|
)
|
|
statistique07: Optional[str] = Field(
|
|
None, description="Statistique 7 (CT_Statistique07)"
|
|
)
|
|
statistique08: Optional[str] = Field(
|
|
None, description="Statistique 8 (CT_Statistique08)"
|
|
)
|
|
statistique09: Optional[str] = Field(
|
|
None, description="Statistique 9 (CT_Statistique09)"
|
|
)
|
|
statistique10: Optional[str] = Field(
|
|
None, description="Statistique 10 (CT_Statistique10)"
|
|
)
|
|
|
|
encours_autorise: Optional[float] = Field(
|
|
None, description="Encours maximum autorisé (CT_Encours)"
|
|
)
|
|
assurance_credit: Optional[float] = Field(
|
|
None, description="Montant assurance crédit (CT_Assurance)"
|
|
)
|
|
langue: Optional[int] = Field(
|
|
None, description="Code langue 0=FR, 1=EN (CT_Langue)"
|
|
)
|
|
commercial_code: Optional[int] = Field(
|
|
None, description="Code du commercial (CO_No)"
|
|
)
|
|
|
|
lettrage_auto: Optional[bool] = Field(
|
|
None, description="Lettrage automatique (CT_Lettrage)"
|
|
)
|
|
est_actif: Optional[bool] = Field(None, description="True si actif (CT_Sommeil=0)")
|
|
type_facture: Optional[int] = Field(
|
|
None, description="Type facture 0=Facture, 1=BL (CT_Facture)"
|
|
)
|
|
est_prospect: Optional[bool] = Field(
|
|
None, description="True si prospect (CT_Prospect=1)"
|
|
)
|
|
bl_en_facture: Optional[int] = Field(
|
|
None, description="Imprimer BL en facture (CT_BLFact)"
|
|
)
|
|
saut_page: Optional[int] = Field(
|
|
None, description="Saut de page sur documents (CT_Saut)"
|
|
)
|
|
validation_echeance: Optional[int] = Field(
|
|
None, description="Valider les échéances (CT_ValidEch)"
|
|
)
|
|
controle_encours: Optional[int] = Field(
|
|
None, description="Contrôler l'encours (CT_ControlEnc)"
|
|
)
|
|
exclure_relance: Optional[bool] = Field(
|
|
None, description="Exclure des relances (CT_NotRappel)"
|
|
)
|
|
exclure_penalites: Optional[bool] = Field(
|
|
None, description="Exclure des pénalités (CT_NotPenal)"
|
|
)
|
|
bon_a_payer: Optional[int] = Field(
|
|
None, description="Bon à payer obligatoire (CT_BonAPayer)"
|
|
)
|
|
|
|
priorite_livraison: Optional[int] = Field(
|
|
None, description="Priorité livraison (CT_PrioriteLivr)"
|
|
)
|
|
livraison_partielle: Optional[int] = Field(
|
|
None, description="Livraison partielle (CT_LivrPartielle)"
|
|
)
|
|
delai_transport: Optional[int] = Field(
|
|
None, description="Délai transport jours (CT_DelaiTransport)"
|
|
)
|
|
delai_appro: Optional[int] = Field(
|
|
None, description="Délai appro jours (CT_DelaiAppro)"
|
|
)
|
|
|
|
commentaire: Optional[str] = Field(
|
|
None, description="Commentaire libre (CT_Commentaire)"
|
|
)
|
|
|
|
section_analytique: Optional[str] = Field(
|
|
None, description="Section analytique (CA_Num)"
|
|
)
|
|
|
|
mode_reglement_code: Optional[int] = Field(
|
|
None, description="Code mode règlement (MR_No)"
|
|
)
|
|
surveillance_active: Optional[bool] = Field(
|
|
None, description="Surveillance financière (CT_Surveillance)"
|
|
)
|
|
coface: Optional[str] = Field(None, description="Code Coface 25 car. (CT_Coface)")
|
|
forme_juridique: Optional[str] = Field(
|
|
None, description="Forme juridique SA, SARL (CT_SvFormeJuri)"
|
|
)
|
|
effectif: Optional[str] = Field(
|
|
None, description="Nombre d'employés (CT_SvEffectif)"
|
|
)
|
|
sv_regularite: Optional[str] = Field(
|
|
None, description="Régularité paiements (CT_SvRegul)"
|
|
)
|
|
sv_cotation: Optional[str] = Field(
|
|
None, description="Cotation crédit (CT_SvCotation)"
|
|
)
|
|
sv_objet_maj: Optional[str] = Field(
|
|
None, description="Objet dernière MAJ (CT_SvObjetMaj)"
|
|
)
|
|
sv_chiffre_affaires: Optional[float] = Field(
|
|
None, description="Chiffre d'affaires (CT_SvCA)"
|
|
)
|
|
sv_resultat: Optional[float] = Field(
|
|
None, description="Résultat financier (CT_SvResultat)"
|
|
)
|
|
|
|
compte_general: Optional[str] = Field(
|
|
None, description="Compte général principal (CG_NumPrinc)"
|
|
)
|
|
categorie_tarif: Optional[int] = Field(
|
|
None, description="Catégorie tarifaire (N_CatTarif)"
|
|
)
|
|
categorie_compta: Optional[int] = Field(
|
|
None, description="Catégorie comptable (N_CatCompta)"
|
|
)
|
|
|
|
contacts: Optional[List[Contact]] = Field(
|
|
default_factory=list, description="Liste des contacts du client"
|
|
)
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"numero": "CLI000001",
|
|
"intitule": "SARL EXEMPLE",
|
|
"type_tiers": 0,
|
|
"qualite": "CLI",
|
|
"classement": "A",
|
|
"raccourci": "EXEMPL",
|
|
"siret": "12345678901234",
|
|
"tva_intra": "FR12345678901",
|
|
"code_naf": "6201Z",
|
|
"contact": "Jean Dupont",
|
|
"adresse": "123 Rue de la Paix",
|
|
"complement": "Bâtiment B",
|
|
"code_postal": "75001",
|
|
"ville": "Paris",
|
|
"region": "Île-de-France",
|
|
"pays": "France",
|
|
"telephone": "0123456789",
|
|
"telecopie": "0123456788",
|
|
"email": "contact@exemple.fr",
|
|
"site_web": "https://www.exemple.fr",
|
|
"facebook": "https://facebook.com/exemple",
|
|
"linkedin": "https://linkedin.com/company/exemple",
|
|
"taux01": 0.0,
|
|
"taux02": 0.0,
|
|
"taux03": 0.0,
|
|
"taux04": 0.0,
|
|
"statistique01": "Informatique",
|
|
"statistique02": "",
|
|
"statistique03": "",
|
|
"statistique04": "",
|
|
"statistique05": "",
|
|
"statistique06": "",
|
|
"statistique07": "",
|
|
"statistique08": "",
|
|
"statistique09": "",
|
|
"statistique10": "",
|
|
"encours_autorise": 50000.0,
|
|
"assurance_credit": 40000.0,
|
|
"langue": 0,
|
|
"commercial_code": 1,
|
|
"lettrage_auto": True,
|
|
"est_actif": True,
|
|
"type_facture": 1,
|
|
"est_prospect": False,
|
|
"bl_en_facture": 0,
|
|
"saut_page": 0,
|
|
"validation_echeance": 0,
|
|
"controle_encours": 1,
|
|
"exclure_relance": False,
|
|
"exclure_penalites": False,
|
|
"bon_a_payer": 0,
|
|
"priorite_livraison": 1,
|
|
"livraison_partielle": 1,
|
|
"delai_transport": 2,
|
|
"delai_appro": 0,
|
|
"commentaire": "Client important",
|
|
"section_analytique": "",
|
|
"mode_reglement_code": 1,
|
|
"surveillance_active": True,
|
|
"coface": "COF12345",
|
|
"forme_juridique": "SARL",
|
|
"effectif": "50-99",
|
|
"sv_regularite": "",
|
|
"sv_cotation": "",
|
|
"sv_objet_maj": "",
|
|
"sv_chiffre_affaires": 2500000.0,
|
|
"sv_resultat": 150000.0,
|
|
"compte_general": "4110000",
|
|
"categorie_tarif": 0,
|
|
"categorie_compta": 0,
|
|
}
|
|
}
|
|
|
|
|
|
class ClientCreateRequest(BaseModel):
|
|
intitule: str = Field(
|
|
..., max_length=69, description="Nom du client (CT_Intitule) - OBLIGATOIRE"
|
|
)
|
|
|
|
numero: str = Field(
|
|
..., 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 ClientUpdateRequest(BaseModel):
|
|
"""
|
|
Modèle pour modification d'un client existant
|
|
TOUS les champs de ClientCreateRequest sont modifiables
|
|
TOUS optionnels (seuls les champs fournis sont modifiés)
|
|
"""
|
|
|
|
intitule: Optional[str] = Field(None, max_length=69)
|
|
qualite: Optional[str] = Field(None, max_length=17)
|
|
classement: Optional[str] = Field(None, max_length=17)
|
|
raccourci: Optional[str] = Field(None, max_length=7)
|
|
|
|
siret: Optional[str] = Field(None, max_length=15)
|
|
tva_intra: Optional[str] = Field(None, max_length=25)
|
|
code_naf: Optional[str] = Field(None, max_length=7)
|
|
|
|
contact: Optional[str] = Field(None, max_length=35)
|
|
adresse: Optional[str] = Field(None, max_length=35)
|
|
complement: Optional[str] = Field(None, max_length=35)
|
|
code_postal: Optional[str] = Field(None, max_length=9)
|
|
ville: Optional[str] = Field(None, max_length=35)
|
|
region: Optional[str] = Field(None, max_length=25)
|
|
pays: Optional[str] = Field(None, max_length=35)
|
|
|
|
telephone: Optional[str] = Field(None, max_length=21)
|
|
telecopie: Optional[str] = Field(None, max_length=21)
|
|
email: Optional[str] = Field(None, max_length=69)
|
|
site_web: Optional[str] = Field(None, max_length=69)
|
|
portable: Optional[str] = Field(None, max_length=21)
|
|
facebook: Optional[str] = Field(None, max_length=69)
|
|
linkedin: Optional[str] = Field(None, max_length=69)
|
|
|
|
compte_general: Optional[str] = Field(None, max_length=13)
|
|
|
|
categorie_tarifaire: Optional[str] = None
|
|
categorie_comptable: Optional[str] = None
|
|
|
|
taux01: Optional[float] = None
|
|
taux02: Optional[float] = None
|
|
taux03: Optional[float] = None
|
|
taux04: Optional[float] = None
|
|
|
|
secteur: Optional[str] = Field(None, max_length=21)
|
|
statistique01: Optional[str] = Field(None, max_length=21)
|
|
statistique02: Optional[str] = Field(None, max_length=21)
|
|
statistique03: Optional[str] = Field(None, max_length=21)
|
|
statistique04: Optional[str] = Field(None, max_length=21)
|
|
statistique05: Optional[str] = Field(None, max_length=21)
|
|
statistique06: Optional[str] = Field(None, max_length=21)
|
|
statistique07: Optional[str] = Field(None, max_length=21)
|
|
statistique08: Optional[str] = Field(None, max_length=21)
|
|
statistique09: Optional[str] = Field(None, max_length=21)
|
|
statistique10: Optional[str] = Field(None, max_length=21)
|
|
|
|
encours_autorise: Optional[float] = None
|
|
assurance_credit: Optional[float] = None
|
|
langue: Optional[int] = Field(None, ge=0)
|
|
commercial_code: Optional[int] = None
|
|
|
|
lettrage_auto: Optional[bool] = None
|
|
est_actif: Optional[bool] = None
|
|
type_facture: Optional[int] = Field(None, ge=0, le=2)
|
|
est_prospect: Optional[bool] = None
|
|
bl_en_facture: Optional[int] = Field(None, ge=0, le=1)
|
|
saut_page: Optional[int] = Field(None, ge=0, le=1)
|
|
validation_echeance: Optional[int] = Field(None, ge=0, le=1)
|
|
controle_encours: Optional[int] = Field(None, ge=0, le=1)
|
|
exclure_relance: Optional[int] = Field(None, ge=0, le=1)
|
|
exclure_penalites: Optional[int] = Field(None, ge=0, le=1)
|
|
bon_a_payer: Optional[int] = Field(None, ge=0, le=1)
|
|
|
|
priorite_livraison: Optional[int] = Field(None, ge=0, le=5)
|
|
livraison_partielle: Optional[int] = Field(None, ge=0, le=1)
|
|
delai_transport: Optional[int] = Field(None, ge=0)
|
|
delai_appro: Optional[int] = Field(None, ge=0)
|
|
|
|
commentaire: Optional[str] = Field(None, max_length=35)
|
|
|
|
section_analytique: Optional[str] = Field(None, max_length=13)
|
|
|
|
mode_reglement_code: Optional[int] = None
|
|
|
|
surveillance_active: Optional[int] = Field(None, ge=0, le=1)
|
|
coface: Optional[str] = Field(None, max_length=25)
|
|
forme_juridique: Optional[str] = Field(None, max_length=33)
|
|
effectif: Optional[str] = Field(None, max_length=11)
|
|
sv_regularite: Optional[str] = Field(None, max_length=3)
|
|
sv_cotation: Optional[str] = Field(None, max_length=5)
|
|
sv_objet_maj: Optional[str] = Field(None, max_length=61)
|
|
ca_annuel: Optional[float] = None
|
|
sv_chiffre_affaires: Optional[float] = None
|
|
sv_resultat: Optional[float] = None
|
|
|
|
@field_validator("siret")
|
|
@classmethod
|
|
def validate_siret(cls, v):
|
|
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):
|
|
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):
|
|
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):
|
|
if isinstance(v, str) and v.lower() in ("none", "null", ""):
|
|
return None
|
|
return v
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"email": "nouveau@email.fr",
|
|
"telephone": "0198765432",
|
|
"portable": "0687654321",
|
|
"adresse": "456 Avenue Nouvelle",
|
|
"ville": "Lyon",
|
|
}
|
|
}
|