Extracted Pydantic schema
This commit is contained in:
parent
d6d01fee9f
commit
2ea7fa8371
16 changed files with 2241 additions and 1867 deletions
932
main.py
932
main.py
|
|
@ -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}")
|
||||||
|
|
|
||||||
1083
sage_connector.py
1083
sage_connector.py
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||||
]
|
]
|
||||||
111
schemas/articles/articles.py
Normal file
111
schemas/articles/articles.py
Normal 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")
|
||||||
19
schemas/articles/famille_d_articles.py
Normal file
19
schemas/articles/famille_d_articles.py
Normal 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)"
|
||||||
|
)
|
||||||
22
schemas/documents/avoirs.py
Normal file
22
schemas/documents/avoirs.py
Normal 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
|
||||||
21
schemas/documents/commandes.py
Normal file
21
schemas/documents/commandes.py
Normal 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
|
||||||
19
schemas/documents/devis.py
Normal file
19
schemas/documents/devis.py
Normal 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
|
||||||
32
schemas/documents/documents.py
Normal file
32
schemas/documents/documents.py
Normal 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")
|
||||||
|
|
||||||
22
schemas/documents/factures.py
Normal file
22
schemas/documents/factures.py
Normal 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
|
||||||
21
schemas/documents/livraisons.py
Normal file
21
schemas/documents/livraisons.py
Normal 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
|
||||||
21
schemas/others/general_schema.py
Normal file
21
schemas/others/general_schema.py
Normal 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
410
schemas/tiers/clients.py
Normal 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
43
schemas/tiers/contact.py
Normal 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
|
||||||
41
schemas/tiers/fournisseurs.py
Normal file
41
schemas/tiers/fournisseurs.py
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in a new issue