Refactored Create Client Object
This commit is contained in:
parent
a4827c0534
commit
3809d3403b
1 changed files with 451 additions and 208 deletions
659
api.py
659
api.py
|
|
@ -4,7 +4,7 @@ from fastapi.responses import StreamingResponse
|
|||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from enum import Enum, IntEnum
|
||||
from decimal import Decimal
|
||||
import uvicorn
|
||||
from contextlib import asynccontextmanager
|
||||
|
|
@ -401,275 +401,518 @@ class BaremeRemiseResponse(BaseModel):
|
|||
message: str
|
||||
|
||||
|
||||
class TypeTiers(IntEnum):
|
||||
"""CT_Type - Type de tiers"""
|
||||
CLIENT = 0
|
||||
FOURNISSEUR = 1
|
||||
SALARIE = 2
|
||||
AUTRE = 3
|
||||
|
||||
|
||||
class ClientCreateAPIRequest(BaseModel):
|
||||
"""Modèle complet pour la création d'un client Sage avec tous les champs disponibles"""
|
||||
"""
|
||||
Modèle complet pour la création d'un client Sage 100c
|
||||
Noms alignés sur le frontend + mapping vers champs Sage
|
||||
"""
|
||||
|
||||
# ========================================
|
||||
# CHAMPS OBLIGATOIRES
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# IDENTIFICATION PRINCIPALE
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
intitule: str = Field(..., max_length=69, description="Nom du client (CT_Intitule)")
|
||||
numero: Optional[str] = Field(None, max_length=17, description="Numéro client CT_Num (auto si vide)")
|
||||
type_tiers: Optional[int] = Field(0, ge=0, le=3, description="CT_Type: 0=Client, 1=Fournisseur, 2=Salarié, 3=Autre")
|
||||
qualite: 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")
|
||||
|
||||
# ========================================
|
||||
# IDENTIFICATION & CLASSIFICATION
|
||||
# ========================================
|
||||
num: Optional[str] = Field(None, max_length=17, description="Numéro client (auto si vide)")
|
||||
compte_collectif: str = Field("411000", max_length=13, description="Compte général (CG_NumPrinc)")
|
||||
qualite: str = Field("CLI", max_length=3, description="CLI/FOU/SAL/DIV/AUT")
|
||||
classement: Optional[str] = Field(None, max_length=17, description="Code de classement")
|
||||
raccourci: Optional[str] = Field(None, max_length=17, description="Code abrégé")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# STATUTS & FLAGS
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
est_prospect: bool = Field(False, description="CT_Prospect")
|
||||
est_actif: bool = Field(True, description="Inverse de CT_Sommeil")
|
||||
est_en_sommeil: Optional[bool] = Field(None, description="CT_Sommeil (calculé depuis est_actif si None)")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# INFORMATIONS ENTREPRISE / PERSONNE
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
est_entreprise: Optional[bool] = Field(None, description="Flag interne - pas de champ Sage direct")
|
||||
est_particulier: Optional[bool] = Field(None, description="Flag interne - pas de champ Sage direct")
|
||||
forme_juridique: Optional[str] = Field(None, max_length=33, description="CT_SvFormeJuri")
|
||||
civilite: Optional[str] = Field(None, max_length=17, description="Stocké dans CT_Qualite ou champ libre")
|
||||
nom: Optional[str] = Field(None, max_length=35, description="Pour particuliers - partie de CT_Intitule")
|
||||
prenom: Optional[str] = Field(None, max_length=35, description="Pour particuliers - partie de CT_Intitule")
|
||||
nom_complet: Optional[str] = Field(None, max_length=69, description="Calculé ou CT_Intitule")
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# ADRESSE PRINCIPALE
|
||||
# ========================================
|
||||
contact: Optional[str] = Field(None, max_length=69, description="Nom du contact principal")
|
||||
adresse: Optional[str] = Field(None, max_length=35, description="Adresse ligne 1")
|
||||
complement: Optional[str] = Field(None, max_length=35, description="Adresse ligne 2")
|
||||
code_postal: Optional[str] = Field(None, max_length=9, description="Code postal")
|
||||
ville: Optional[str] = Field(None, max_length=35, description="Ville")
|
||||
code_region: Optional[str] = Field(None, max_length=25, description="Code région/département")
|
||||
pays: Optional[str] = Field(None, max_length=35, description="Pays")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
contact: Optional[str] = Field(None, max_length=35, description="CT_Contact")
|
||||
adresse: Optional[str] = Field(None, max_length=35, description="CT_Adresse")
|
||||
complement: Optional[str] = Field(None, max_length=35, description="CT_Complement")
|
||||
code_postal: Optional[str] = Field(None, max_length=9, description="CT_CodePostal")
|
||||
ville: Optional[str] = Field(None, max_length=35, description="CT_Ville")
|
||||
region: Optional[str] = Field(None, max_length=25, description="CT_CodeRegion")
|
||||
pays: Optional[str] = Field(None, max_length=35, description="CT_Pays")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# CONTACT & COMMUNICATION
|
||||
# ========================================
|
||||
telephone: Optional[str] = Field(None, max_length=21, description="Téléphone principal")
|
||||
telecopie: Optional[str] = Field(None, max_length=21, description="Fax")
|
||||
email: Optional[str] = Field(None, max_length=69, description="Email principal")
|
||||
site: Optional[str] = Field(None, max_length=69, description="Site web")
|
||||
facebook: Optional[str] = Field(None, max_length=100, description="URL Facebook")
|
||||
linkedin: Optional[str] = Field(None, max_length=100, description="URL LinkedIn")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
telephone: Optional[str] = Field(None, max_length=21, description="CT_Telephone")
|
||||
portable: Optional[str] = Field(None, max_length=21, description="Stocké dans statistiques ou contact")
|
||||
telecopie: Optional[str] = Field(None, max_length=21, description="CT_Telecopie")
|
||||
email: Optional[str] = Field(None, max_length=69, description="CT_EMail")
|
||||
site_web: Optional[str] = Field(None, max_length=69, description="CT_Site")
|
||||
facebook: Optional[str] = Field(None, max_length=35, description="CT_Facebook")
|
||||
linkedin: Optional[str] = Field(None, max_length=35, description="CT_LinkedIn")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# IDENTIFIANTS LÉGAUX & FISCAUX
|
||||
# ========================================
|
||||
siret: Optional[str] = Field(None, max_length=14, description="SIRET (14 chiffres)")
|
||||
tva_intra: Optional[str] = Field(None, max_length=25, description="TVA intracommunautaire (CT_Identifiant)")
|
||||
ape: Optional[str] = Field(None, max_length=5, description="Code APE/NAF")
|
||||
type_nif: Optional[int] = Field(None, description="Type de NIF (0-10)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
siret: Optional[str] = Field(None, max_length=15, description="CT_Siret (14-15 chars)")
|
||||
siren: Optional[str] = Field(None, max_length=9, description="Extrait du SIRET")
|
||||
tva_intra: Optional[str] = Field(None, max_length=25, description="CT_Identifiant")
|
||||
code_naf: Optional[str] = Field(None, max_length=7, description="CT_Ape")
|
||||
type_nif: Optional[int] = Field(None, ge=0, le=10, description="CT_TypeNIF")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# BANQUE & DEVISE
|
||||
# ========================================
|
||||
banque_num: Optional[str] = Field(None, max_length=13, description="Code banque (BT_Num)")
|
||||
devise: Optional[int] = Field(0, description="Code devise (N_Devise, 0=EUR)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
banque_num: Optional[int] = Field(None, description="BT_Num (smallint)")
|
||||
devise: Optional[int] = Field(0, description="N_Devise (0=EUR)")
|
||||
|
||||
# ========================================
|
||||
# CATÉGORIES & CLASSIFICATIONS
|
||||
# ========================================
|
||||
cat_tarif: int = Field(1, ge=1, description="Catégorie tarifaire (N_CatTarif)")
|
||||
cat_compta: int = Field(1, ge=1, description="Catégorie comptable (N_CatCompta)")
|
||||
period: int = Field(1, ge=1, description="Période de règlement (N_Period)")
|
||||
expedition: int = Field(1, ge=1, description="Mode d'expédition (N_Expedition)")
|
||||
condition: int = Field(1, ge=1, description="Condition de livraison (N_Condition)")
|
||||
risque: int = Field(1, ge=1, description="Niveau de risque (N_Risque)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# CATÉGORIES & CLASSIFICATIONS COMMERCIALES
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
categorie_tarifaire: Optional[int] = Field(1, ge=0, description="N_CatTarif")
|
||||
categorie_comptable: Optional[int] = Field(1, ge=0, description="N_CatCompta")
|
||||
periode_reglement: Optional[int] = Field(1, ge=0, description="N_Period")
|
||||
mode_expedition: Optional[int] = Field(1, ge=0, description="N_Expedition")
|
||||
condition_livraison: Optional[int] = Field(1, ge=0, description="N_Condition")
|
||||
niveau_risque: Optional[int] = Field(1, ge=0, description="N_Risque")
|
||||
secteur: Optional[str] = Field(None, max_length=21, description="CT_Statistique01 ou champ libre")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# TAUX PERSONNALISÉS
|
||||
# ========================================
|
||||
taux01: Optional[Decimal] = Field(None, description="Taux personnalisé 1 (CT_Taux01)")
|
||||
taux02: Optional[Decimal] = Field(None, description="Taux personnalisé 2 (CT_Taux02)")
|
||||
taux03: Optional[Decimal] = Field(None, description="Taux personnalisé 3 (CT_Taux03)")
|
||||
taux04: Optional[Decimal] = Field(None, description="Taux personnalisé 4 (CT_Taux04)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
taux01: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_Taux01")
|
||||
taux02: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_Taux02")
|
||||
taux03: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_Taux03")
|
||||
taux04: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_Taux04")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# GESTION COMMERCIALE
|
||||
# ========================================
|
||||
encours: Optional[Decimal] = Field(None, description="Encours autorisé (CT_Encours)")
|
||||
assurance: Optional[Decimal] = Field(None, description="Plafond assurance (CT_Assurance)")
|
||||
num_payeur: Optional[str] = Field(None, max_length=17, description="Numéro client payeur")
|
||||
langue: Optional[int] = Field(None, description="Code langue (0-25)")
|
||||
langue_iso2: Optional[str] = Field(None, max_length=2, description="Code ISO langue (FR, EN, etc)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
encours_autorise: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_Encours")
|
||||
assurance_credit: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_Assurance")
|
||||
num_payeur: Optional[str] = Field(None, max_length=17, description="CT_NumPayeur")
|
||||
langue: Optional[int] = Field(None, ge=0, description="CT_Langue")
|
||||
langue_iso2: Optional[str] = Field(None, max_length=3, description="CT_LangueISO2")
|
||||
commercial_code: Optional[int] = Field(None, description="CO_No (int)")
|
||||
commercial_nom: Optional[str] = Field(None, description="Résolu depuis CO_No - non stocké")
|
||||
effectif: Optional[str] = Field(None, max_length=11, description="CT_SvEffectif")
|
||||
ca_annuel: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_SvCA")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# COMPTABILITÉ
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
compte_general: Optional[str] = Field("411000", max_length=13, description="CG_NumPrinc")
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# PARAMÈTRES FACTURATION
|
||||
# ========================================
|
||||
facture: int = Field(1, description="Type facturation (0=aucune, 1=normale, 2=regroupée)")
|
||||
bl_fact: Optional[int] = Field(None, description="BL en facture (0/1)")
|
||||
saut: Optional[int] = Field(None, description="Saut de page (0/1)")
|
||||
lettrage: bool = Field(True, description="Lettrage auto (CT_Lettrage)")
|
||||
valid_ech: Optional[int] = Field(None, description="Validation échéance (0/1)")
|
||||
control_enc: Optional[int] = Field(None, description="Contrôle encours (0/1)")
|
||||
not_rappel: Optional[int] = Field(None, description="Pas de relance (0/1)")
|
||||
not_penal: Optional[int] = Field(None, description="Pas de pénalités (0/1)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
type_facture: Optional[int] = Field(1, ge=0, le=2, description="CT_Facture: 0=aucune, 1=normale, 2=regroupée")
|
||||
bl_en_facture: Optional[int] = Field(None, ge=0, le=1, description="CT_BLFact")
|
||||
saut_page: Optional[int] = Field(None, ge=0, le=1, description="CT_Saut")
|
||||
lettrage_auto: Optional[bool] = Field(True, description="CT_Lettrage")
|
||||
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")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# LIVRAISON & LOGISTIQUE
|
||||
# ========================================
|
||||
priorite_livr: Optional[int] = Field(None, description="Priorité livraison (0-5)")
|
||||
livr_partielle: Optional[int] = Field(None, description="Livraison partielle autorisée (0/1)")
|
||||
delai_transport: Optional[int] = Field(None, description="Délai transport (jours)")
|
||||
delai_appro: Optional[int] = Field(None, description="Délai approvisionnement (jours)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
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)")
|
||||
|
||||
# JOURS DE COMMANDE (0=non, 1=oui)
|
||||
order_day_lundi: Optional[int] = Field(None, ge=0, le=1)
|
||||
order_day_mardi: Optional[int] = Field(None, ge=0, le=1)
|
||||
order_day_mercredi: Optional[int] = Field(None, ge=0, le=1)
|
||||
order_day_jeudi: Optional[int] = Field(None, ge=0, le=1)
|
||||
order_day_vendredi: Optional[int] = Field(None, ge=0, le=1)
|
||||
order_day_samedi: Optional[int] = Field(None, ge=0, le=1)
|
||||
order_day_dimanche: Optional[int] = Field(None, ge=0, le=1)
|
||||
# JOURS DE COMMANDE (0=non, 1=oui) - CT_OrderDay01-07
|
||||
jours_commande: Optional[dict] = Field(
|
||||
None,
|
||||
description="Dict avec clés: lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche"
|
||||
)
|
||||
|
||||
# JOURS DE LIVRAISON (0=non, 1=oui)
|
||||
delivery_day_lundi: Optional[int] = Field(None, ge=0, le=1)
|
||||
delivery_day_mardi: Optional[int] = Field(None, ge=0, le=1)
|
||||
delivery_day_mercredi: Optional[int] = Field(None, ge=0, le=1)
|
||||
delivery_day_jeudi: Optional[int] = Field(None, ge=0, le=1)
|
||||
delivery_day_vendredi: Optional[int] = Field(None, ge=0, le=1)
|
||||
delivery_day_samedi: Optional[int] = Field(None, ge=0, le=1)
|
||||
delivery_day_dimanche: Optional[int] = Field(None, ge=0, le=1)
|
||||
# JOURS DE LIVRAISON (0=non, 1=oui) - CT_DeliveryDay01-07
|
||||
jours_livraison: Optional[dict] = Field(
|
||||
None,
|
||||
description="Dict avec clés: lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche"
|
||||
)
|
||||
|
||||
# ========================================
|
||||
# DATES FERMETURE
|
||||
# ========================================
|
||||
date_ferme_debut: Optional[date] = Field(None, description="Début période fermeture")
|
||||
date_ferme_fin: Optional[date] = Field(None, description="Fin période fermeture")
|
||||
date_fermeture_debut: Optional[date] = Field(None, description="CT_DateFermeDebut")
|
||||
date_fermeture_fin: Optional[date] = Field(None, description="CT_DateFermeFin")
|
||||
|
||||
# ========================================
|
||||
# STATISTIQUES PERSONNALISÉES (10 champs)
|
||||
# ========================================
|
||||
statistique01: Optional[str] = Field(None, max_length=69)
|
||||
statistique02: Optional[str] = Field(None, max_length=69)
|
||||
statistique03: Optional[str] = Field(None, max_length=69)
|
||||
statistique04: Optional[str] = Field(None, max_length=69)
|
||||
statistique05: Optional[str] = Field(None, max_length=69)
|
||||
statistique06: Optional[str] = Field(None, max_length=69)
|
||||
statistique07: Optional[str] = Field(None, max_length=69)
|
||||
statistique08: Optional[str] = Field(None, max_length=69)
|
||||
statistique09: Optional[str] = Field(None, max_length=69)
|
||||
statistique10: Optional[str] = Field(None, max_length=69)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# STATISTIQUES PERSONNALISÉES (10 champs de 21 chars max)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
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")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# COMMENTAIRE
|
||||
# ========================================
|
||||
commentaire: Optional[str] = Field(None, description="Commentaire libre (CT_Commentaire, jusqu'à 2Go théorique)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
commentaire: Optional[str] = Field(None, max_length=35, description="CT_Commentaire")
|
||||
|
||||
# ========================================
|
||||
# ÉTAT & STATUT
|
||||
# ========================================
|
||||
sommeil: bool = Field(False, description="Compte en sommeil")
|
||||
prospect: bool = Field(False, description="Prospect (pas encore client)")
|
||||
bon_a_payer: Optional[int] = Field(None, description="Bon à payer (0/1)")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# ANALYTIQUE
|
||||
# ========================================
|
||||
section_analytique: Optional[str] = Field(None, max_length=13, description="Section analytique (CA_Num)")
|
||||
section_analytique_ifrs: Optional[str] = Field(None, max_length=13, description="Section IFRS (CA_NumIFRS)")
|
||||
plan_analytique: Optional[int] = Field(None, description="Plan analytique (N_Analytique)")
|
||||
plan_analytique_ifrs: Optional[int] = Field(None, description="Plan IFRS (N_AnalytiqueIFRS)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
section_analytique: Optional[str] = Field(None, max_length=13, description="CA_Num")
|
||||
section_analytique_ifrs: Optional[str] = Field(None, max_length=13, description="CA_NumIFRS")
|
||||
plan_analytique: Optional[int] = Field(None, ge=0, description="N_Analytique")
|
||||
plan_analytique_ifrs: Optional[int] = Field(None, ge=0, description="N_AnalytiqueIFRS")
|
||||
|
||||
# ========================================
|
||||
# COLLABORATEUR & DÉPÔT
|
||||
# ========================================
|
||||
collaborateur: Optional[str] = Field(None, max_length=4, description="Code collaborateur (CO_No)")
|
||||
depot: Optional[str] = Field(None, max_length=13, description="Dépôt par défaut (DE_No)")
|
||||
etablissement: Optional[str] = Field(None, max_length=13, description="Établissement (EB_No)")
|
||||
mode_regl: Optional[str] = Field(None, max_length=3, description="Mode règlement (MR_No)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# ORGANISATION
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
depot_code: Optional[int] = Field(None, description="DE_No (int)")
|
||||
etablissement_code: Optional[int] = Field(None, description="EB_No (int)")
|
||||
mode_reglement_code: Optional[int] = Field(None, description="MR_No (int)")
|
||||
calendrier_code: Optional[int] = Field(None, description="CAL_No (int)")
|
||||
num_centrale: Optional[str] = Field(None, max_length=17, description="CT_NumCentrale")
|
||||
|
||||
# ========================================
|
||||
# CENTRALE D'ACHAT
|
||||
# ========================================
|
||||
num_centrale: Optional[str] = Field(None, max_length=17, description="Numéro centrale d'achat")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# SURVEILLANCE COFACE
|
||||
# ========================================
|
||||
coface: Optional[str] = Field(None, max_length=25, description="Code Coface")
|
||||
surveillance: Optional[int] = Field(None, description="Surveillance activée (0/1)")
|
||||
sv_date_create: Optional[date] = Field(None, description="Date création entreprise")
|
||||
sv_forme_juri: Optional[str] = Field(None, max_length=50, description="Forme juridique")
|
||||
sv_effectif: Optional[int] = Field(None, description="Effectif")
|
||||
sv_ca: Optional[Decimal] = Field(None, description="Chiffre d'affaires")
|
||||
sv_resultat: Optional[Decimal] = Field(None, description="Résultat")
|
||||
sv_incident: Optional[int] = Field(None, description="Incidents (0/1)")
|
||||
sv_date_incid: Optional[date] = Field(None, description="Date dernier incident")
|
||||
sv_privil: Optional[int] = Field(None, description="Privilèges (0/1)")
|
||||
sv_regul: Optional[int] = Field(None, description="Régularité (0/1)")
|
||||
sv_cotation: Optional[str] = Field(None, max_length=25, description="Cotation")
|
||||
sv_date_maj: Optional[date] = Field(None, description="Date MAJ surveillance")
|
||||
sv_objet_maj: Optional[str] = Field(None, max_length=35, description="Objet MAJ")
|
||||
sv_date_bilan: Optional[date] = Field(None, description="Date dernier bilan")
|
||||
sv_nb_mois_bilan: Optional[int] = Field(None, description="Nb mois bilan")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
coface: Optional[str] = Field(None, max_length=25, description="CT_Coface")
|
||||
surveillance_active: Optional[int] = Field(None, ge=0, le=1, description="CT_Surveillance")
|
||||
sv_date_creation: Optional[datetime] = Field(None, description="CT_SvDateCreate")
|
||||
sv_chiffre_affaires: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_SvCA")
|
||||
sv_resultat: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="CT_SvResultat")
|
||||
sv_incident: Optional[int] = Field(None, ge=0, le=1, description="CT_SvIncident")
|
||||
sv_date_incident: Optional[datetime] = Field(None, description="CT_SvDateIncid")
|
||||
sv_privilege: Optional[int] = Field(None, ge=0, le=1, description="CT_SvPrivil")
|
||||
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_date_maj: Optional[datetime] = Field(None, description="CT_SvDateMaj")
|
||||
sv_objet_maj: Optional[str] = Field(None, max_length=61, description="CT_SvObjetMaj")
|
||||
sv_date_bilan: Optional[datetime] = Field(None, description="CT_SvDateBilan")
|
||||
sv_nb_mois_bilan: Optional[int] = Field(None, ge=0, description="CT_SvNbMoisBilan")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# FACTURATION ÉLECTRONIQUE
|
||||
# ========================================
|
||||
facture_elec: Optional[int] = Field(None, description="Facturation électronique (0/1)")
|
||||
edi_code_type: Optional[int] = Field(None, description="Type code EDI")
|
||||
edi_code: Optional[str] = Field(None, max_length=35, description="Code EDI")
|
||||
edi_code_sage: Optional[str] = Field(None, max_length=35, description="Code EDI Sage")
|
||||
fe_assujetti: Optional[int] = Field(None, description="Assujetti facture électronique")
|
||||
fe_autre_identif_type: Optional[str] = Field(None, max_length=10)
|
||||
fe_autre_identif_val: Optional[str] = Field(None, max_length=50)
|
||||
fe_entite_type: Optional[int] = Field(None)
|
||||
fe_emission: Optional[int] = Field(None)
|
||||
fe_application: Optional[int] = Field(None)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
facture_electronique: Optional[int] = Field(None, ge=0, le=1, description="CT_FactureElec")
|
||||
edi_code_type: Optional[int] = Field(None, description="CT_EdiCodeType")
|
||||
edi_code: Optional[str] = Field(None, max_length=23, description="CT_EdiCode")
|
||||
edi_code_sage: Optional[str] = Field(None, max_length=9, description="CT_EdiCodeSage")
|
||||
fe_assujetti: Optional[int] = Field(None, description="CT_FEAssujetti")
|
||||
fe_autre_identif_type: Optional[int] = Field(None, description="CT_FEAutreIdentifType")
|
||||
fe_autre_identif_val: Optional[str] = Field(None, max_length=81, description="CT_FEAutreIdentifVal")
|
||||
fe_entite_type: Optional[int] = Field(None, description="CT_FEEntiteType")
|
||||
fe_emission: Optional[int] = Field(None, description="CT_FEEmission")
|
||||
fe_application: Optional[int] = Field(None, description="CT_FEApplication")
|
||||
fe_date_synchro: Optional[datetime] = Field(None, description="CT_FEDateSynchro")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# ÉCHANGES & INTÉGRATION
|
||||
# ========================================
|
||||
echange_rappro: Optional[int] = Field(None, description="Échange rapprochement")
|
||||
echange_cr: Optional[int] = Field(None, description="Échange compte rendu")
|
||||
annulation_cr: Optional[int] = Field(None, description="Annulation CR")
|
||||
profil_soc: Optional[int] = Field(None, description="Profil société")
|
||||
statut_contrat: Optional[int] = Field(None, description="Statut contrat")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
echange_rappro: Optional[int] = Field(None, description="CT_EchangeRappro")
|
||||
echange_cr: Optional[int] = Field(None, description="CT_EchangeCR")
|
||||
pi_no_echange: Optional[int] = Field(None, description="PI_NoEchange")
|
||||
annulation_cr: Optional[int] = Field(None, description="CT_AnnulationCR")
|
||||
profil_societe: Optional[int] = Field(None, description="CT_ProfilSoc")
|
||||
statut_contrat: Optional[int] = Field(None, description="CT_StatutContrat")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# RGPD & CONFIDENTIALITÉ
|
||||
# ========================================
|
||||
gdpr: Optional[int] = Field(None, description="Consentement RGPD (0/1)")
|
||||
exclure_trait: Optional[int] = Field(None, description="Exclure des traitements (0/1)")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
rgpd_consentement: Optional[int] = Field(None, ge=0, le=1, description="CT_GDPR")
|
||||
exclure_traitement: Optional[int] = Field(None, ge=0, le=1, description="CT_ExclureTrait")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# REPRÉSENTANT FISCAL
|
||||
# ========================================
|
||||
represent_int: Optional[int] = Field(None, description="Représentant intracommunautaire")
|
||||
represent_nif: Optional[str] = Field(None, max_length=25, description="NIF représentant")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
representant_intl: Optional[str] = Field(None, max_length=35, description="CT_RepresentInt")
|
||||
representant_nif: Optional[str] = Field(None, max_length=25, description="CT_RepresentNIF")
|
||||
|
||||
# ========================================
|
||||
# CHAMPS PERSONNALISÉS (exemples)
|
||||
# ========================================
|
||||
date_creation_societe: Optional[date] = Field(None, description="Date création de la société")
|
||||
capital_social: Optional[Decimal] = Field(None, description="Capital social")
|
||||
actionnaire_principal: Optional[str] = Field(None, max_length=100, description="Actionnaire principal")
|
||||
score_banque_france: Optional[str] = Field(None, max_length=10, description="Score BDF")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# CHAMPS PERSONNALISÉS (Info Libres Sage)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
date_creation_societe: Optional[datetime] = Field(None, description="Date création société (Info Libre)")
|
||||
capital_social: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6, description="Capital social")
|
||||
actionnaire_principal: Optional[str] = Field(None, max_length=69, description="Actionnaire Pal")
|
||||
score_banque_france: Optional[str] = Field(None, max_length=14, description="Score Banque de France")
|
||||
|
||||
# FIDÉLITÉ
|
||||
total_points_fidelite: Optional[int] = Field(None, description="Total points fidélité")
|
||||
points_fidelite_restants: Optional[int] = Field(None, description="Points restants")
|
||||
fin_validite_carte: Optional[date] = Field(None, description="Fin validité carte")
|
||||
total_points_fidelite: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6)
|
||||
points_fidelite_restants: Optional[Decimal] = Field(None, max_digits=24, decimal_places=6)
|
||||
date_fin_carte_fidelite: Optional[datetime] = Field(None, description="Fin validité carte fidélité")
|
||||
date_negociation_reglement: Optional[datetime] = Field(None, description="Date négociation règlement")
|
||||
|
||||
# ========================================
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# AUTRES
|
||||
# ========================================
|
||||
calendrier: Optional[str] = Field(None, max_length=13, description="Code calendrier (CAL_No)")
|
||||
mode_test: Optional[int] = Field(None, description="Mode test (0/1)")
|
||||
confiance: Optional[int] = Field(None, description="Niveau de confiance")
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
mode_test: Optional[int] = Field(None, ge=0, le=1, description="CT_ModeTest")
|
||||
confiance: Optional[int] = Field(None, description="CT_Confiance")
|
||||
dn_id: Optional[str] = Field(None, max_length=37, description="DN_Id")
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# MÉTADONNÉES (en lecture seule généralement)
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
date_creation: Optional[datetime] = Field(None, description="cbCreation - géré par Sage")
|
||||
date_modification: Optional[datetime] = Field(None, description="cbModification - géré par Sage")
|
||||
date_maj: Optional[datetime] = Field(None, description="CT_DateMAJ")
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# VALIDATORS
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
|
||||
@field_validator('siret')
|
||||
@classmethod
|
||||
def validate_siret(cls, v):
|
||||
if v and len(v.replace(' ', '')) != 14:
|
||||
raise ValueError('Le SIRET doit contenir 14 chiffres')
|
||||
return v.replace(' ', '') if v else v
|
||||
if v and v.lower() not in ('none', ''):
|
||||
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('siren')
|
||||
@classmethod
|
||||
def validate_siren(cls, v):
|
||||
if v and v.lower() not in ('none', ''):
|
||||
cleaned = v.replace(' ', '')
|
||||
if len(cleaned) != 9:
|
||||
raise ValueError('Le SIREN doit contenir 9 caractères')
|
||||
return cleaned
|
||||
return None
|
||||
|
||||
@field_validator('email')
|
||||
@classmethod
|
||||
def validate_email(cls, v):
|
||||
if v and '@' not in v:
|
||||
raise ValueError('Format email invalide')
|
||||
if v and v.lower() not in ('none', ''):
|
||||
if '@' not in v:
|
||||
raise ValueError('Format email invalide')
|
||||
return v.strip()
|
||||
return None
|
||||
|
||||
@field_validator('adresse', 'code_postal', 'ville', 'pays', 'telephone', 'tva_intra', mode='before')
|
||||
@classmethod
|
||||
def clean_none_strings(cls, v):
|
||||
"""Convertit les chaînes 'None' en None"""
|
||||
if isinstance(v, str) and v.lower() in ('none', 'null', ''):
|
||||
return None
|
||||
return v
|
||||
|
||||
@field_validator('est_en_sommeil', mode='before')
|
||||
@classmethod
|
||||
def compute_sommeil(cls, v, info):
|
||||
"""Calcule est_en_sommeil depuis est_actif si non fourni"""
|
||||
if v is None and 'est_actif' in info.data:
|
||||
return not info.data.get('est_actif', True)
|
||||
return v
|
||||
|
||||
def to_sage_dict(self) -> dict:
|
||||
"""Convertit le modèle en dictionnaire compatible avec la méthode creer_client"""
|
||||
return {
|
||||
# Identification
|
||||
"intitule": self.intitule,
|
||||
"num": self.numero,
|
||||
"type_tiers": self.type_tiers,
|
||||
"qualite": self.qualite,
|
||||
"classement": self.classement,
|
||||
"raccourci": self.raccourci,
|
||||
|
||||
# Statuts
|
||||
"prospect": self.est_prospect,
|
||||
"sommeil": not self.est_actif if self.est_en_sommeil is None else self.est_en_sommeil,
|
||||
|
||||
# Adresse
|
||||
"contact": self.contact,
|
||||
"adresse": self.adresse,
|
||||
"complement": self.complement,
|
||||
"code_postal": self.code_postal,
|
||||
"ville": self.ville,
|
||||
"code_region": self.region,
|
||||
"pays": self.pays,
|
||||
|
||||
# Communication
|
||||
"telephone": self.telephone,
|
||||
"telecopie": self.telecopie,
|
||||
"email": self.email,
|
||||
"site": self.site_web,
|
||||
"facebook": self.facebook,
|
||||
"linkedin": self.linkedin,
|
||||
|
||||
# Identifiants légaux
|
||||
"siret": self.siret,
|
||||
"tva_intra": self.tva_intra,
|
||||
"ape": self.code_naf,
|
||||
"type_nif": self.type_nif,
|
||||
|
||||
# Banque & devise
|
||||
"banque_num": self.banque_num,
|
||||
"devise": self.devise,
|
||||
|
||||
# Catégories
|
||||
"cat_tarif": self.categorie_tarifaire or 1,
|
||||
"cat_compta": self.categorie_comptable or 1,
|
||||
"period": self.periode_reglement or 1,
|
||||
"expedition": self.mode_expedition or 1,
|
||||
"condition": self.condition_livraison or 1,
|
||||
"risque": self.niveau_risque or 1,
|
||||
|
||||
# Taux
|
||||
"taux01": self.taux01,
|
||||
"taux02": self.taux02,
|
||||
"taux03": self.taux03,
|
||||
"taux04": self.taux04,
|
||||
|
||||
# Gestion commerciale
|
||||
"encours": self.encours_autorise,
|
||||
"assurance": self.assurance_credit,
|
||||
"num_payeur": self.num_payeur,
|
||||
"langue": self.langue,
|
||||
"langue_iso2": self.langue_iso2,
|
||||
"compte_collectif": self.compte_general or "411000",
|
||||
"collaborateur": self.commercial_code,
|
||||
|
||||
# Facturation
|
||||
"facture": self.type_facture,
|
||||
"bl_fact": self.bl_en_facture,
|
||||
"saut": self.saut_page,
|
||||
"lettrage": self.lettrage_auto,
|
||||
"valid_ech": self.validation_echeance,
|
||||
"control_enc": self.controle_encours,
|
||||
"not_rappel": self.exclure_relance,
|
||||
"not_penal": self.exclure_penalites,
|
||||
"bon_a_payer": self.bon_a_payer,
|
||||
|
||||
# Livraison
|
||||
"priorite_livr": self.priorite_livraison,
|
||||
"livr_partielle": self.livraison_partielle,
|
||||
"delai_transport": self.delai_transport,
|
||||
"delai_appro": self.delai_appro,
|
||||
"date_ferme_debut": self.date_fermeture_debut,
|
||||
"date_ferme_fin": self.date_fermeture_fin,
|
||||
|
||||
# Jours commande/livraison
|
||||
**(self._expand_jours("order_day", self.jours_commande) if self.jours_commande else {}),
|
||||
**(self._expand_jours("delivery_day", self.jours_livraison) if self.jours_livraison else {}),
|
||||
|
||||
# Statistiques
|
||||
"statistique01": self.statistique01 or self.secteur,
|
||||
"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,
|
||||
|
||||
# Commentaire
|
||||
"commentaire": self.commentaire,
|
||||
|
||||
# Analytique
|
||||
"section_analytique": self.section_analytique,
|
||||
"section_analytique_ifrs": self.section_analytique_ifrs,
|
||||
"plan_analytique": self.plan_analytique,
|
||||
"plan_analytique_ifrs": self.plan_analytique_ifrs,
|
||||
|
||||
# Organisation
|
||||
"depot": self.depot_code,
|
||||
"etablissement": self.etablissement_code,
|
||||
"mode_regl": self.mode_reglement_code,
|
||||
"calendrier": self.calendrier_code,
|
||||
"num_centrale": self.num_centrale,
|
||||
|
||||
# Surveillance
|
||||
"coface": self.coface,
|
||||
"surveillance": self.surveillance_active,
|
||||
"sv_forme_juri": self.forme_juridique,
|
||||
"sv_effectif": self.effectif,
|
||||
"sv_ca": self.sv_chiffre_affaires or self.ca_annuel,
|
||||
"sv_resultat": self.sv_resultat,
|
||||
"sv_incident": self.sv_incident,
|
||||
"sv_date_incid": self.sv_date_incident,
|
||||
"sv_privil": self.sv_privilege,
|
||||
"sv_regul": self.sv_regularite,
|
||||
"sv_cotation": self.sv_cotation,
|
||||
"sv_date_create": self.sv_date_creation,
|
||||
"sv_date_maj": self.sv_date_maj,
|
||||
"sv_objet_maj": self.sv_objet_maj,
|
||||
"sv_date_bilan": self.sv_date_bilan,
|
||||
"sv_nb_mois_bilan": self.sv_nb_mois_bilan,
|
||||
|
||||
# Facturation électronique
|
||||
"facture_elec": self.facture_electronique,
|
||||
"edi_code_type": self.edi_code_type,
|
||||
"edi_code": self.edi_code,
|
||||
"edi_code_sage": self.edi_code_sage,
|
||||
"fe_assujetti": self.fe_assujetti,
|
||||
"fe_autre_identif_type": self.fe_autre_identif_type,
|
||||
"fe_autre_identif_val": self.fe_autre_identif_val,
|
||||
"fe_entite_type": self.fe_entite_type,
|
||||
"fe_emission": self.fe_emission,
|
||||
"fe_application": self.fe_application,
|
||||
|
||||
# Échanges
|
||||
"echange_rappro": self.echange_rappro,
|
||||
"echange_cr": self.echange_cr,
|
||||
"annulation_cr": self.annulation_cr,
|
||||
"profil_soc": self.profil_societe,
|
||||
"statut_contrat": self.statut_contrat,
|
||||
|
||||
# RGPD
|
||||
"gdpr": self.rgpd_consentement,
|
||||
"exclure_trait": self.exclure_traitement,
|
||||
|
||||
# Représentant
|
||||
"represent_int": self.representant_intl,
|
||||
"represent_nif": self.representant_nif,
|
||||
|
||||
# Autres
|
||||
"mode_test": self.mode_test,
|
||||
"confiance": self.confiance,
|
||||
}
|
||||
|
||||
def _expand_jours(self, prefix: str, jours: dict) -> dict:
|
||||
"""Expand les jours en champs individuels"""
|
||||
mapping = {
|
||||
"lundi": f"{prefix}_lundi",
|
||||
"mardi": f"{prefix}_mardi",
|
||||
"mercredi": f"{prefix}_mercredi",
|
||||
"jeudi": f"{prefix}_jeudi",
|
||||
"vendredi": f"{prefix}_vendredi",
|
||||
"samedi": f"{prefix}_samedi",
|
||||
"dimanche": f"{prefix}_dimanche",
|
||||
}
|
||||
return {v: jours.get(k) for k, v in mapping.items() if jours.get(k) is not None}
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"intitule": "ENTREPRISE EXEMPLE SARL",
|
||||
"num": "CLI00123",
|
||||
"compte_collectif": "411000",
|
||||
"qualite": "CLI"
|
||||
"numero": "CLI00123",
|
||||
"compte_general": "411000",
|
||||
"qualite": "CLI",
|
||||
"est_prospect": False,
|
||||
"est_actif": True
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ClientUpdateRequest(BaseModel):
|
||||
"""Modèle pour modification d'un client existant"""
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue