Updated client's field
This commit is contained in:
parent
215763b679
commit
f8ea7b48b9
2 changed files with 268 additions and 58 deletions
295
api.py
295
api.py
|
|
@ -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(
|
|
||||||
None, max_length=17, description="Code client souhaité (auto si vide)"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Adresse
|
# ========================================
|
||||||
adresse: Optional[str] = Field(None, max_length=35)
|
# IDENTIFICATION & CLASSIFICATION
|
||||||
code_postal: Optional[str] = Field(None, max_length=9)
|
# ========================================
|
||||||
ville: Optional[str] = Field(None, max_length=35)
|
num: Optional[str] = Field(None, max_length=17, description="Numéro client (auto si vide)")
|
||||||
pays: Optional[str] = Field(None, max_length=35)
|
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é")
|
||||||
|
|
||||||
# Contact
|
# ========================================
|
||||||
email: Optional[EmailStr] = None
|
# ADRESSE PRINCIPALE
|
||||||
telephone: Optional[str] = Field(None, max_length=21, description="Téléphone fixe")
|
# ========================================
|
||||||
portable: Optional[str] = Field(None, max_length=21, description="Téléphone mobile")
|
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")
|
||||||
|
|
||||||
# Juridique
|
# ========================================
|
||||||
forme_juridique: Optional[str] = Field(
|
# CONTACT & COMMUNICATION
|
||||||
None, max_length=50, description="SARL, SA, SAS, EI, etc."
|
# ========================================
|
||||||
)
|
telephone: Optional[str] = Field(None, max_length=21, description="Téléphone principal")
|
||||||
siret: Optional[str] = Field(None, max_length=14)
|
telecopie: Optional[str] = Field(None, max_length=21, description="Fax")
|
||||||
tva_intra: Optional[str] = Field(None, max_length=25)
|
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"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,6 +11,10 @@ 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é")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue