Updated client's field

This commit is contained in:
Fanilo-Nantenaina 2025-12-24 11:41:31 +03:00
parent 215763b679
commit f8ea7b48b9
2 changed files with 268 additions and 58 deletions

305
api.py
View file

@ -400,54 +400,275 @@ class BaremeRemiseResponse(BaseModel):
message: str message: str
class ClientCreateAPIRequest(BaseModel): class ClientCreateRequest(BaseModel):
"""Modèle pour création d'un nouveau client""" """Modèle complet pour la création d'un client Sage avec tous les champs disponibles"""
intitule: str = Field( # ========================================
..., min_length=1, max_length=69, description="Raison sociale ou Nom complet" # CHAMPS OBLIGATOIRES
) # ========================================
compte_collectif: str = Field( intitule: str = Field(..., max_length=69, description="Nom du client (CT_Intitule)")
"411000", description="Compte comptable (411000 par défaut)"
) # ========================================
num: Optional[str] = Field( # IDENTIFICATION & CLASSIFICATION
None, max_length=17, description="Code client souhaité (auto si vide)" # ========================================
) 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)")
# Adresse qualite: str = Field("CLI", max_length=3, description="CLI/FOU/SAL/DIV/AUT")
adresse: Optional[str] = Field(None, max_length=35) classement: Optional[str] = Field(None, max_length=17, description="Code de classement")
code_postal: Optional[str] = Field(None, max_length=9) raccourci: Optional[str] = Field(None, max_length=17, description="Code abrégé")
ville: Optional[str] = Field(None, max_length=35)
pays: Optional[str] = Field(None, max_length=35) # ========================================
# ADRESSE PRINCIPALE
# Contact # ========================================
email: Optional[EmailStr] = None contact: Optional[str] = Field(None, max_length=69, description="Nom du contact principal")
telephone: Optional[str] = Field(None, max_length=21, description="Téléphone fixe") adresse: Optional[str] = Field(None, max_length=35, description="Adresse ligne 1")
portable: Optional[str] = Field(None, max_length=21, description="Téléphone mobile") complement: Optional[str] = Field(None, max_length=35, description="Adresse ligne 2")
code_postal: Optional[str] = Field(None, max_length=9, description="Code postal")
# Juridique ville: Optional[str] = Field(None, max_length=35, description="Ville")
forme_juridique: Optional[str] = Field( code_region: Optional[str] = Field(None, max_length=25, description="Code région/département")
None, max_length=50, description="SARL, SA, SAS, EI, etc." pays: Optional[str] = Field(None, max_length=35, description="Pays")
)
siret: Optional[str] = Field(None, max_length=14) # ========================================
tva_intra: Optional[str] = Field(None, max_length=25) # 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")
# ========================================
# 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)")
# ========================================
# 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)")
# ========================================
# 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)")
# ========================================
# 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)")
# ========================================
# 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)")
# ========================================
# 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)")
# ========================================
# 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)")
# 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 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)
# ========================================
# 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")
# ========================================
# 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)
# ========================================
# COMMENTAIRE
# ========================================
commentaire: Optional[str] = Field(None, description="Commentaire libre (CT_Commentaire, jusqu'à 2Go théorique)")
# ========================================
# É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)")
# ========================================
# 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)")
# ========================================
# 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")
# ========================================
# 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)
# ========================================
# É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")
# ========================================
# RGPD & CONFIDENTIALITÉ
# ========================================
gdpr: Optional[int] = Field(None, description="Consentement RGPD (0/1)")
exclure_trait: Optional[int] = Field(None, description="Exclure des traitements (0/1)")
# ========================================
# 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")
# ========================================
# 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")
# 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")
# ========================================
# 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")
@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
@field_validator('email')
@classmethod
def validate_email(cls, v):
if v and '@' not in v:
raise ValueError('Format email invalide')
return v
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
"intitule": "SARL NOUVELLE ENTREPRISE", "intitule": "ENTREPRISE EXEMPLE SARL",
"forme_juridique": "SARL", "num": "CLI00123",
"adresse": "10 Avenue des Champs", "compte_collectif": "411000",
"code_postal": "75008", "qualite": "CLI"
"ville": "Paris",
"telephone": "0123456789",
"portable": "0612345678",
"email": "contact@nouvelle-entreprise.fr",
"siret": "12345678901234",
"tva_intra": "FR12345678901",
} }
} }
class ClientUpdateRequest(BaseModel): class ClientUpdateRequest(BaseModel):
"""Modèle pour modification d'un client existant""" """Modèle pour modification d'un client existant"""

View file

@ -1,9 +1,3 @@
# -*- coding: utf-8 -*-
"""
Queue d'envoi d'emails avec threading et génération PDF
Version VPS Linux - utilise sage_client pour récupérer les données
"""
import threading import threading
import queue import queue
import time import time
@ -17,7 +11,11 @@ from email.mime.text import MIMEText
from email.mime.application import MIMEApplication from email.mime.application import MIMEApplication
from config import settings from config import settings
import logging import logging
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm
from io import BytesIO
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -202,15 +200,6 @@ class EmailQueue:
await asyncio.to_thread(self._send_smtp, msg) await asyncio.to_thread(self._send_smtp, msg)
def _generate_pdf(self, doc_id: str, type_doc: int) -> bytes: def _generate_pdf(self, doc_id: str, type_doc: int) -> bytes:
"""
Génération PDF via ReportLab + sage_client
Cette méthode est appelée depuis un thread worker
"""
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm
from io import BytesIO
if not self.sage_client: if not self.sage_client:
logger.error("❌ sage_client non configuré") logger.error("❌ sage_client non configuré")