functionnal cree_client

This commit is contained in:
mickael 2025-12-24 21:00:49 +01:00
parent 7b451223ef
commit 57c05082c0
2 changed files with 1156 additions and 443 deletions

628
main.py
View file

@ -1,9 +1,10 @@
from fastapi import FastAPI, HTTPException, Header, Depends, Query from fastapi import FastAPI, HTTPException, Header, Depends, Query
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field, validator from pydantic import BaseModel, Field, validator, EmailStr, field_validator
from typing import Optional, List, Dict from typing import Optional, List, Dict
from datetime import datetime, date from datetime import datetime, date
from enum import Enum from decimal import Decimal
from enum import Enum, IntEnum
import uvicorn import uvicorn
import logging import logging
import win32com.client import win32com.client
@ -79,20 +80,518 @@ class StatutRequest(BaseModel):
nouveau_statut: int nouveau_statut: int
class ClientCreateRequest(BaseModel): class TypeTiers(IntEnum):
intitule: str = Field(..., description="Nom du client (CT_Intitule)") """CT_Type - Type de tiers"""
compte_collectif: str = Field("411000", description="Compte général rattaché") CLIENT = 0
num: Optional[str] = Field(None, description="Laisser vide pour numérotation auto") FOURNISSEUR = 1
adresse: Optional[str] = None SALARIE = 2
code_postal: Optional[str] = None AUTRE = 3
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 ClientCreateRequest(BaseModel):
"""
Modèle complet pour la création d'un client Sage 100c
Noms alignés sur le frontend + mapping vers champs Sage
"""
# ══════════════════════════════════════════════════════════════
# 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(None, 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")
# ══════════════════════════════════════════════════════════════
# 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=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="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=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[int] = Field(None, description="BT_Num (smallint)")
devise: Optional[int] = Field(0, description="N_Devise (0=EUR)")
# ══════════════════════════════════════════════════════════════
# 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, 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_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
# ══════════════════════════════════════════════════════════════
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_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) - 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) - CT_DeliveryDay01-07
jours_livraison: Optional[dict] = Field(
None,
description="Dict avec clés: lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche"
)
# DATES 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 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, max_length=35, description="CT_Commentaire")
# ══════════════════════════════════════════════════════════════
# ANALYTIQUE
# ══════════════════════════════════════════════════════════════
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")
# ══════════════════════════════════════════════════════════════
# 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")
# ══════════════════════════════════════════════════════════════
# SURVEILLANCE COFACE
# ══════════════════════════════════════════════════════════════
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_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="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É
# ══════════════════════════════════════════════════════════════
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
# ══════════════════════════════════════════════════════════════
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 (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[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
# ══════════════════════════════════════════════════════════════
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 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 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",
"numero": "CLI00123",
"compte_general": "411000",
"qualite": "CLI",
"est_prospect": False,
"est_actif": True
}
}
class ClientUpdateGatewayRequest(BaseModel): class ClientUpdateGatewayRequest(BaseModel):
"""Modèle pour modification client côté gateway""" """Modèle pour modification client côté gateway"""
@ -1627,107 +2126,6 @@ def lire_mouvement_stock(numero: str):
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@app.get("/sage/modeles/list")
def lister_modeles_disponibles():
"""Liste tous les modèles .bgc disponibles pour chaque type de document"""
try:
modeles = sage.lister_modeles_crystal()
return {"success": True, "data": modeles}
except Exception as e:
logger.error(f" Erreur listage modèles: {e}")
raise HTTPException(500, str(e))
@app.get("/sage/documents/{numero}/pdf", dependencies=[Depends(verify_token)])
def generer_pdf_document(
numero: str,
type_doc: int = Query(..., description="Type document (0=devis, 60=facture, etc.)"),
modele: str = Query(None, description="Nom du modèle .bgc (optionnel)"),
base64_encode: bool = Query(True, description="Retourner en base64"),
):
"""
Génère un PDF d'un document Sage avec le modèle spécifié
"""
try:
# LOG pour debug
logger.info(
f" PDF Request: numero={numero}, type={type_doc}, modele={modele}, base64={base64_encode}"
)
# Générer le PDF
pdf_bytes = sage.generer_pdf_document(
numero=numero, type_doc=type_doc, modele=modele
)
if not pdf_bytes:
raise HTTPException(404, f"Impossible de générer le PDF pour {numero}")
# LOG taille PDF
logger.info(f" PDF généré: {len(pdf_bytes)} octets")
if base64_encode:
# Retour en JSON avec base64
import base64
pdf_base64 = base64.b64encode(pdf_bytes).decode("utf-8")
return {
"success": True,
"data": {
"numero": numero,
"type": type_doc,
"modele": modele or "défaut",
"pdf_base64": pdf_base64,
"size_bytes": len(pdf_bytes),
"size_readable": f"{len(pdf_bytes) / 1024:.1f} KB",
},
}
else:
# Retour direct du fichier PDF
from fastapi.responses import Response
return Response(
content=pdf_bytes,
media_type="application/pdf",
headers={
"Content-Disposition": f'inline; filename="{numero}.pdf"',
"Content-Length": str(len(pdf_bytes)), # Taille explicite
},
)
except HTTPException:
raise
except ValueError as e:
logger.error(f" Erreur métier: {e}")
raise HTTPException(400, str(e))
except Exception as e:
logger.error(f" Erreur technique: {e}", exc_info=True)
raise HTTPException(500, str(e))
@app.get("/sage/object-exploration")
async def explorer_objets_impression_sage(modele:str="Devis client avec détail projet.bgc"):
try:
dossier = r"C:\Users\Public\Documents\Sage\Entreprise 100c\fr-FR\Documents standards\Gestion commerciale\Ventes"
chemin = os.path.join(dossier, modele)
if not os.path.exists(chemin):
return {"error": f"Fichier non trouve: {modele}"}
expliration = sage.analyser_bgc_complet(chemin)
if not expliration:
raise HTTPException(404, f"ERROR")
return {"success": True, "data": expliration}
except HTTPException:
raise
except Exception as e:
logger.error(f" Erreur exploration : {e}")
raise HTTPException(500, str(e))
# ===================================================== # =====================================================
# LANCEMENT # LANCEMENT
# ===================================================== # =====================================================

File diff suppressed because it is too large Load diff