From a4f5274663ace070c527483e05e07651224bee2d Mon Sep 17 00:00:00 2001 From: Fanilo-Nantenaina Date: Sat, 27 Dec 2025 06:12:10 +0300 Subject: [PATCH] refactor(api): align ArticleResponse model with Sage database structure --- api.py | 292 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 230 insertions(+), 62 deletions(-) diff --git a/api.py b/api.py index 6995bb5..602361c 100644 --- a/api.py +++ b/api.py @@ -528,16 +528,9 @@ class FournisseurDetails(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)") 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( None, description="Code EAN / Code-barres principal (AR_CodeBarre)" @@ -545,12 +538,12 @@ class ArticleResponse(BaseModel): code_barre: Optional[str] = Field( None, description="Code-barres (alias de code_ean)" ) + edi_code: Optional[str] = Field( + None, description="Code EDI (AR_EdiCode)" + ) raccourci: Optional[str] = Field( None, description="Code raccourci (AR_Raccourci)" ) - racine: Optional[str] = Field( - None, description="Racine de référence (AR_Racine)" - ) prix_vente: float = Field( ..., ge=0, description="Prix de vente HT unitaire (AR_PrixVen)" @@ -558,15 +551,29 @@ class ArticleResponse(BaseModel): prix_achat: Optional[float] = Field( 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( 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( default=0.0, description="Stock réel total (F_ARTSTOCK.AS_QteSto)" @@ -589,32 +596,42 @@ class ArticleResponse(BaseModel): suivi_stock: Optional[bool] = Field( 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( 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( None, max_length=10, description="Unité de poids (AR_UnitePoids)" ) - poids: Optional[float] = Field( - None, ge=0, description="Poids net unitaire (AR_Poids, AR_PoidsNet)" + poids_net: Optional[float] = Field( + None, ge=0, description="Poids net unitaire en kg (AR_PoidsNet)" ) 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( None, ge=0, 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( 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)" ) 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( - 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( - 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( - None, description="Compte comptable de vente de la famille (F_FAMILLE.CG_NumVte)" + famille_garantie: Optional[int] = Field( + None, description="Garantie de la famille (F_FAMILLE.FA_Garantie)" ) - famille_compte_achat: Optional[str] = Field( - None, description="Compte comptable d'achat de la famille (F_FAMILLE.CG_NumAch)" + famille_unite_poids: Optional[str] = Field( + 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( @@ -651,9 +692,15 @@ class ArticleResponse(BaseModel): garantie: Optional[int] = Field( 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( - 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( None, description="Nom du fournisseur principal (F_COMPTET.CT_Intitule)" @@ -664,8 +711,8 @@ class ArticleResponse(BaseModel): nb_colis: Optional[int] = Field( None, ge=0, description="Nombre de colis par unité (AR_NbColis)" ) - article_substitut: Optional[str] = Field( - None, description="Référence article de substitution (AR_Substitut)" + prevision: Optional[bool] = Field( + None, description="Gestion en prévision (AR_Prevision)" ) est_actif: bool = Field( @@ -674,9 +721,16 @@ class ArticleResponse(BaseModel): en_sommeil: bool = Field( 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( None, description="Soumis à escompte (AR_Escompte)" ) + delai: Optional[int] = Field( + None, description="Délai de livraison en jours (AR_Delai)" + ) + publie: Optional[bool] = Field( None, description="Publié sur web/catalogue (AR_Publie)" ) @@ -689,6 +743,21 @@ class ArticleResponse(BaseModel): non_imprimable: Optional[bool] = Field( 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( None, description="Article fictif (AR_Fictif)" ) @@ -699,9 +768,45 @@ class ArticleResponse(BaseModel): None, ge=0, description="Niveau de criticité (AR_Criticite)" ) - code_fiscal: Optional[str] = Field( - None, max_length=10, description="Code fiscal/TVA (AR_CodeFiscal)" + reprise_code_defaut: Optional[str] = Field( + 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( None, description="Code TVA (F_TAXE.TA_Code)" ) @@ -728,47 +833,111 @@ class ArticleResponse(BaseModel): 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( 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: json_schema_extra = { "example": { - "reference": "ART-001", - "designation": "Ordinateur portable 15 pouces", - "designation_complementaire": "Intel i7, 16GB RAM, 512GB SSD", + "reference": "BAGUE-001", + "designation": "Bague Or 18K Diamant", "code_ean": "3760123456789", - "prix_vente": 899.00, - "prix_achat": 650.00, - "prix_revient": 675.50, - "stock_reel": 25.0, - "stock_mini": 5.0, - "stock_maxi": 50.0, - "stock_disponible": 22.0, + "edi_code": "EAN13", + "raccourci": "BAG001", + "prix_vente": 1299.00, + "prix_achat": 850.00, + "coef": 1.53, + "prix_net": 1199.00, + "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", - "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_libelle": "Article", - "famille_code": "INFO", - "famille_libelle": "Informatique", - "fournisseur_principal": 101, - "fournisseur_nom": "TechSupply SAS", + "famille_code": "BAGUE", + "famille_libelle": "Bagues", + "famille_type": 0, + "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, "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_taux": 20.0, - "date_creation": "2023-01-15", - "date_modification": "2024-11-20" + "stat_01": "Luxe", + "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): """Réponse pour une liste d'articles""" @@ -793,7 +962,6 @@ class ArticleListResponse(BaseModel): "avec_stock": True, "avec_famille": True, "articles": [ - # Exemple d'article (voir ArticleResponse) ] } }