refactor(api): align ArticleResponse model with Sage database structure

This commit is contained in:
Fanilo-Nantenaina 2025-12-27 06:12:10 +03:00
parent be7a8baddd
commit a4f5274663

292
api.py
View file

@ -528,16 +528,9 @@ class FournisseurDetails(BaseModel):
class ArticleResponse(BaseModel): class ArticleResponse(BaseModel):
"""Modèle de réponse pour un article avec toutes ses informations"""
reference: str = Field(..., description="Référence article (AR_Ref)") reference: str = Field(..., description="Référence article (AR_Ref)")
designation: str = Field(..., description="Désignation principale (AR_Design)") designation: str = Field(..., description="Désignation principale (AR_Design)")
designation_complementaire: Optional[str] = Field(
None, description="Désignation complémentaire (AR_Design2)"
)
description: Optional[str] = Field(
None, description="Description détaillée / Commentaire (AR_Commentaire, AR_Info)"
)
code_ean: Optional[str] = Field( code_ean: Optional[str] = Field(
None, description="Code EAN / Code-barres principal (AR_CodeBarre)" None, description="Code EAN / Code-barres principal (AR_CodeBarre)"
@ -545,12 +538,12 @@ class ArticleResponse(BaseModel):
code_barre: Optional[str] = Field( code_barre: Optional[str] = Field(
None, description="Code-barres (alias de code_ean)" None, description="Code-barres (alias de code_ean)"
) )
edi_code: Optional[str] = Field(
None, description="Code EDI (AR_EdiCode)"
)
raccourci: Optional[str] = Field( raccourci: Optional[str] = Field(
None, description="Code raccourci (AR_Raccourci)" None, description="Code raccourci (AR_Raccourci)"
) )
racine: Optional[str] = Field(
None, description="Racine de référence (AR_Racine)"
)
prix_vente: float = Field( prix_vente: float = Field(
..., ge=0, description="Prix de vente HT unitaire (AR_PrixVen)" ..., ge=0, description="Prix de vente HT unitaire (AR_PrixVen)"
@ -558,15 +551,29 @@ class ArticleResponse(BaseModel):
prix_achat: Optional[float] = Field( prix_achat: Optional[float] = Field(
None, ge=0, description="Prix d'achat HT (AR_PrixAch)" None, ge=0, description="Prix d'achat HT (AR_PrixAch)"
) )
prix_revient: Optional[float] = Field(
None, ge=0, description="Prix de revient (AR_PrixRev)"
)
prix_ttc: Optional[float] = Field(
None, ge=0, description="Prix de vente TTC (AR_PrixTTC)"
)
coef: Optional[float] = Field( coef: Optional[float] = Field(
None, ge=0, description="Coefficient multiplicateur (AR_Coef)" None, ge=0, description="Coefficient multiplicateur (AR_Coef)"
) )
prix_net: Optional[float] = Field(
None, ge=0, description="Prix unitaire net (AR_PUNet)"
)
prix_achat_nouveau: Optional[float] = Field(
None, ge=0, description="Nouveau prix d'achat à venir (AR_PrixAchNouv)"
)
coef_nouveau: Optional[float] = Field(
None, ge=0, description="Nouveau coefficient à venir (AR_CoefNouv)"
)
prix_vente_nouveau: Optional[float] = Field(
None, ge=0, description="Nouveau prix de vente à venir (AR_PrixVenNouv)"
)
date_application_prix: Optional[str] = Field(
None, description="Date d'application des nouveaux prix (AR_DateApplication)"
)
cout_standard: Optional[float] = Field(
None, ge=0, description="Coût standard (AR_CoutStd)"
)
stock_reel: float = Field( stock_reel: float = Field(
default=0.0, description="Stock réel total (F_ARTSTOCK.AS_QteSto)" default=0.0, description="Stock réel total (F_ARTSTOCK.AS_QteSto)"
@ -589,32 +596,42 @@ class ArticleResponse(BaseModel):
suivi_stock: Optional[bool] = Field( suivi_stock: Optional[bool] = Field(
None, description="Suivi de stock activé (AR_SuiviStock)" None, description="Suivi de stock activé (AR_SuiviStock)"
) )
nomenclature: Optional[bool] = Field(
None, description="Article avec nomenclature (AR_Nomencl)"
)
qte_composant: Optional[float] = Field(
None, description="Quantité de composant (AR_QteComp)"
)
qte_operatoire: Optional[float] = Field(
None, description="Quantité opératoire (AR_QteOperatoire)"
)
unite_vente: Optional[str] = Field( unite_vente: Optional[str] = Field(
None, max_length=10, description="Unité de vente (AR_UniteVen)" None, max_length=10, description="Unité de vente (AR_UniteVen)"
) )
unite_achat: Optional[str] = Field(
None, max_length=10, description="Unité d'achat/stock (AR_Unite)"
)
unite_poids: Optional[str] = Field( unite_poids: Optional[str] = Field(
None, max_length=10, description="Unité de poids (AR_UnitePoids)" None, max_length=10, description="Unité de poids (AR_UnitePoids)"
) )
poids: Optional[float] = Field( poids_net: Optional[float] = Field(
None, ge=0, description="Poids net unitaire (AR_Poids, AR_PoidsNet)" None, ge=0, description="Poids net unitaire en kg (AR_PoidsNet)"
) )
poids_brut: Optional[float] = Field( poids_brut: Optional[float] = Field(
None, ge=0, description="Poids brut unitaire (AR_PoidsBrut)" None, ge=0, description="Poids brut unitaire en kg (AR_PoidsBrut)"
) )
volume: Optional[float] = Field(
None, ge=0, description="Volume unitaire en m³ (AR_Volume)" gamme_1: Optional[str] = Field(
None, description="Énumération gamme 1 (AR_Gamme1)"
)
gamme_2: Optional[str] = Field(
None, description="Énumération gamme 2 (AR_Gamme2)"
) )
type_article: Optional[int] = Field( type_article: Optional[int] = Field(
None, None,
ge=0, ge=0,
le=3, le=3,
description="Type d'article : 0=Article, 1=Prestation, 2=Divers, 3=Nomenclature (AR_Type)" description="Type : 0=Article, 1=Prestation, 2=Divers, 3=Nomenclature (AR_Type)"
) )
type_article_libelle: Optional[str] = Field( type_article_libelle: Optional[str] = Field(
None, description="Libellé du type d'article" None, description="Libellé du type d'article"
@ -630,19 +647,43 @@ class ArticleResponse(BaseModel):
None, description="Type de famille : 0=Détail, 1=Total (F_FAMILLE.FA_Type)" None, description="Type de famille : 0=Détail, 1=Total (F_FAMILLE.FA_Type)"
) )
famille_unite_vente: Optional[str] = Field( famille_unite_vente: Optional[str] = Field(
None, description="Unité de vente héritée de la famille (F_FAMILLE.FA_UniteVen)" None, description="Unité de vente de la famille (F_FAMILLE.FA_UniteVen)"
) )
famille_coef: Optional[float] = Field( famille_coef: Optional[float] = Field(
None, description="Coefficient hérité de la famille (F_FAMILLE.FA_Coef)" None, description="Coefficient de la famille (F_FAMILLE.FA_Coef)"
) )
famille_suivi_stock: Optional[bool] = Field( famille_suivi_stock: Optional[bool] = Field(
None, description="Suivi stock hérité de la famille (F_FAMILLE.FA_SuiviStock)" None, description="Suivi stock de la famille (F_FAMILLE.FA_SuiviStock)"
) )
famille_compte_vente: Optional[str] = Field( famille_garantie: Optional[int] = Field(
None, description="Compte comptable de vente de la famille (F_FAMILLE.CG_NumVte)" None, description="Garantie de la famille (F_FAMILLE.FA_Garantie)"
) )
famille_compte_achat: Optional[str] = Field( famille_unite_poids: Optional[str] = Field(
None, description="Compte comptable d'achat de la famille (F_FAMILLE.CG_NumAch)" None, description="Unité de poids de la famille (F_FAMILLE.FA_UnitePoids)"
)
famille_delai: Optional[int] = Field(
None, description="Délai de la famille (F_FAMILLE.FA_Delai)"
)
famille_nb_colis: Optional[int] = Field(
None, description="Nombre de colis de la famille (F_FAMILLE.FA_NbColis)"
)
famille_code_fiscal: Optional[str] = Field(
None, description="Code fiscal de la famille (F_FAMILLE.FA_CodeFiscal)"
)
famille_escompte: Optional[bool] = Field(
None, description="Escompte de la famille (F_FAMILLE.FA_Escompte)"
)
famille_centrale: Optional[bool] = Field(
None, description="Famille centrale (F_FAMILLE.FA_Central)"
)
famille_nature: Optional[int] = Field(
None, description="Nature de la famille (F_FAMILLE.FA_Nature)"
)
famille_hors_stat: Optional[bool] = Field(
None, description="Hors statistique famille (F_FAMILLE.FA_HorsStat)"
)
famille_pays: Optional[str] = Field(
None, description="Pays de la famille (F_FAMILLE.FA_Pays)"
) )
nature: Optional[int] = Field( nature: Optional[int] = Field(
@ -651,9 +692,15 @@ class ArticleResponse(BaseModel):
garantie: Optional[int] = Field( garantie: Optional[int] = Field(
None, ge=0, description="Durée de garantie en mois (AR_Garantie)" None, ge=0, description="Durée de garantie en mois (AR_Garantie)"
) )
code_fiscal: Optional[str] = Field(
None, max_length=10, description="Code fiscal/TVA (AR_CodeFiscal)"
)
pays: Optional[str] = Field(
None, description="Pays d'origine (AR_Pays)"
)
fournisseur_principal: Optional[int] = Field( fournisseur_principal: Optional[int] = Field(
None, description="N° compte du fournisseur principal (CO_No)" None, description="N° compte du fournisseur principal (CO_No → F_COMPTET.CT_Num)"
) )
fournisseur_nom: Optional[str] = Field( fournisseur_nom: Optional[str] = Field(
None, description="Nom du fournisseur principal (F_COMPTET.CT_Intitule)" None, description="Nom du fournisseur principal (F_COMPTET.CT_Intitule)"
@ -664,8 +711,8 @@ class ArticleResponse(BaseModel):
nb_colis: Optional[int] = Field( nb_colis: Optional[int] = Field(
None, ge=0, description="Nombre de colis par unité (AR_NbColis)" None, ge=0, description="Nombre de colis par unité (AR_NbColis)"
) )
article_substitut: Optional[str] = Field( prevision: Optional[bool] = Field(
None, description="Référence article de substitution (AR_Substitut)" None, description="Gestion en prévision (AR_Prevision)"
) )
est_actif: bool = Field( est_actif: bool = Field(
@ -674,9 +721,16 @@ class ArticleResponse(BaseModel):
en_sommeil: bool = Field( en_sommeil: bool = Field(
default=False, description="Article en sommeil (AR_Sommeil = 1)" default=False, description="Article en sommeil (AR_Sommeil = 1)"
) )
article_substitut: Optional[str] = Field(
None, description="Référence article de substitution (AR_Substitut)"
)
soumis_escompte: Optional[bool] = Field( soumis_escompte: Optional[bool] = Field(
None, description="Soumis à escompte (AR_Escompte)" None, description="Soumis à escompte (AR_Escompte)"
) )
delai: Optional[int] = Field(
None, description="Délai de livraison en jours (AR_Delai)"
)
publie: Optional[bool] = Field( publie: Optional[bool] = Field(
None, description="Publié sur web/catalogue (AR_Publie)" None, description="Publié sur web/catalogue (AR_Publie)"
) )
@ -689,6 +743,21 @@ class ArticleResponse(BaseModel):
non_imprimable: Optional[bool] = Field( non_imprimable: Optional[bool] = Field(
None, description="Non imprimable sur documents (AR_NotImp)" None, description="Non imprimable sur documents (AR_NotImp)"
) )
transfere: Optional[bool] = Field(
None, description="Article transféré (AR_Transfere)"
)
contremarque: Optional[bool] = Field(
None, description="Article en contremarque (AR_Contremarque)"
)
fact_poids: Optional[bool] = Field(
None, description="Facturation au poids (AR_FactPoids)"
)
fact_forfait: Optional[bool] = Field(
None, description="Facturation au forfait (AR_FactForfait)"
)
saisie_variable: Optional[bool] = Field(
None, description="Saisie variable (AR_SaisieVar)"
)
fictif: Optional[bool] = Field( fictif: Optional[bool] = Field(
None, description="Article fictif (AR_Fictif)" None, description="Article fictif (AR_Fictif)"
) )
@ -699,9 +768,45 @@ class ArticleResponse(BaseModel):
None, ge=0, description="Niveau de criticité (AR_Criticite)" None, ge=0, description="Niveau de criticité (AR_Criticite)"
) )
code_fiscal: Optional[str] = Field( reprise_code_defaut: Optional[str] = Field(
None, max_length=10, description="Code fiscal/TVA (AR_CodeFiscal)" None, description="Code reprise par défaut (RP_CodeDefaut)"
) )
delai_fabrication: Optional[int] = Field(
None, description="Délai de fabrication (AR_DelaiFabrication)"
)
delai_peremption: Optional[int] = Field(
None, description="Délai de péremption (AR_DelaiPeremption)"
)
delai_securite: Optional[int] = Field(
None, description="Délai de sécurité (AR_DelaiSecurite)"
)
type_lancement: Optional[int] = Field(
None, description="Type de lancement production (AR_TypeLancement)"
)
cycle: Optional[int] = Field(
None, description="Cycle de production (AR_Cycle)"
)
photo: Optional[str] = Field(
None, description="Chemin/nom du fichier photo (AR_Photo)"
)
langue_1: Optional[str] = Field(
None, description="Texte en langue 1 (AR_Langue1)"
)
langue_2: Optional[str] = Field(
None, description="Texte en langue 2 (AR_Langue2)"
)
frais_01_denomination: Optional[str] = Field(
None, description="Dénomination frais 1 (AR_Frais01FR_Denomination)"
)
frais_02_denomination: Optional[str] = Field(
None, description="Dénomination frais 2 (AR_Frais02FR_Denomination)"
)
frais_03_denomination: Optional[str] = Field(
None, description="Dénomination frais 3 (AR_Frais03FR_Denomination)"
)
tva_code: Optional[str] = Field( tva_code: Optional[str] = Field(
None, description="Code TVA (F_TAXE.TA_Code)" None, description="Code TVA (F_TAXE.TA_Code)"
) )
@ -728,47 +833,111 @@ class ArticleResponse(BaseModel):
None, description="Catégorie comptable 4 (CL_No4)" None, description="Catégorie comptable 4 (CL_No4)"
) )
date_creation: Optional[str] = Field(
None, description="Date de création (AR_DateCre)"
)
date_modification: Optional[str] = Field( date_modification: Optional[str] = Field(
None, description="Date de dernière modification (AR_DateModif)" None, description="Date de dernière modification (AR_DateModif)"
) )
date_sommeil: Optional[str] = Field(
None, description="Date de mise en sommeil (AR_DateSommeil)" marque_commerciale: Optional[str] = Field(
None, description="Marque commerciale (champ personnalisé)"
)
objectif_qtes_vendues: Optional[str] = Field(
None, description="Objectif / Quantités vendues (champ personnalisé)"
)
pourcentage_or: Optional[str] = Field(
None, description="Pourcentage teneur en or (champ personnalisé)"
)
premiere_commercialisation: Optional[str] = Field(
None, description="Date de 1ère commercialisation (champ personnalisé)"
)
interdire_commande: Optional[bool] = Field(
None, description="Interdire la commande (champ personnalisé)"
)
exclure: Optional[bool] = Field(
None, description="Exclure de certains traitements (champ personnalisé)"
) )
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
"reference": "ART-001", "reference": "BAGUE-001",
"designation": "Ordinateur portable 15 pouces", "designation": "Bague Or 18K Diamant",
"designation_complementaire": "Intel i7, 16GB RAM, 512GB SSD",
"code_ean": "3760123456789", "code_ean": "3760123456789",
"prix_vente": 899.00, "edi_code": "EAN13",
"prix_achat": 650.00, "raccourci": "BAG001",
"prix_revient": 675.50, "prix_vente": 1299.00,
"stock_reel": 25.0, "prix_achat": 850.00,
"stock_mini": 5.0, "coef": 1.53,
"stock_maxi": 50.0, "prix_net": 1199.00,
"stock_disponible": 22.0, "prix_achat_nouveau": 875.00,
"date_application_prix": "2025-01-01",
"cout_standard": 860.00,
"stock_reel": 15.0,
"stock_mini": 3.0,
"stock_maxi": 30.0,
"stock_reserve": 2.0,
"stock_commande": 10.0,
"stock_disponible": 13.0,
"suivi_stock": True,
"nomenclature": False,
"unite_vente": "PCE", "unite_vente": "PCE",
"poids": 2.1, "unite_poids": "GR",
"poids_net": 3.5,
"poids_brut": 3.8,
"gamme_1": "Or",
"gamme_2": "18K",
"type_article": 0, "type_article": 0,
"type_article_libelle": "Article", "type_article_libelle": "Article",
"famille_code": "INFO", "famille_code": "BAGUE",
"famille_libelle": "Informatique", "famille_libelle": "Bagues",
"fournisseur_principal": 101, "famille_type": 0,
"fournisseur_nom": "TechSupply SAS", "famille_unite_vente": "PCE",
"famille_coef": 1.5,
"famille_suivi_stock": True,
"famille_garantie": 24,
"nature": 1,
"garantie": 24,
"code_fiscal": "TVA20",
"pays": "FR",
"fournisseur_principal": 150,
"fournisseur_nom": "Bijoux & Co SAS",
"conditionnement": "Écrin",
"nb_colis": 1,
"prevision": False,
"est_actif": True, "est_actif": True,
"en_sommeil": False, "en_sommeil": False,
"soumis_escompte": True,
"delai": 7,
"publie": True,
"hors_statistique": False,
"vente_debit": False,
"non_imprimable": False,
"transfere": False,
"contremarque": False,
"fact_poids": False,
"fact_forfait": False,
"fictif": False,
"sous_traitance": False,
"criticite": 2,
"delai_fabrication": 5,
"photo": "bague001.jpg",
"langue_1": "Gold Ring with Diamond",
"langue_2": "Anillo de Oro con Diamante",
"tva_code": "T20", "tva_code": "T20",
"tva_taux": 20.0, "tva_taux": 20.0,
"date_creation": "2023-01-15", "stat_01": "Luxe",
"date_modification": "2024-11-20" "stat_02": "Féminin",
"categorie_1": 1,
"categorie_2": 5,
"date_modification": "2024-12-15",
"marque_commerciale": "Elite Collection",
"objectif_qtes_vendues": "50",
"pourcentage_or": "75.0",
"premiere_commercialisation": "2024-01-01",
"interdire_commande": False,
"exclure": False
} }
} }
class ArticleListResponse(BaseModel): class ArticleListResponse(BaseModel):
"""Réponse pour une liste d'articles""" """Réponse pour une liste d'articles"""
@ -793,7 +962,6 @@ class ArticleListResponse(BaseModel):
"avec_stock": True, "avec_stock": True,
"avec_famille": True, "avec_famille": True,
"articles": [ "articles": [
# Exemple d'article (voir ArticleResponse)
] ]
} }
} }