Sage100-vps/schemas/articles/articles.py

945 lines
36 KiB
Python

from pydantic import BaseModel, Field, validator, field_validator
from typing import List, Optional
from datetime import date, datetime
from utils import (
NomenclatureType,
SuiviStockType,
TypeArticle,
normalize_enum_to_int,
normalize_string_field,
)
class EmplacementStockModel(BaseModel):
"""Détail du stock dans un emplacement spécifique"""
depot: str = Field(..., description="Numéro du dépôt (DE_No)")
emplacement: str = Field(..., description="Code emplacement (DP_No)")
qte_stockee: float = Field(0.0, description="Quantité stockée (AE_QteSto)")
qte_preparee: float = Field(0.0, description="Quantité préparée (AE_QtePrepa)")
qte_a_controler: float = Field(
0.0, description="Quantité à contrôler (AE_QteAControler)"
)
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
depot_num: Optional[str] = Field(None, description="Numéro dépôt")
depot_nom: Optional[str] = Field(None, description="Nom du dépôt (DE_Intitule)")
depot_code: Optional[str] = Field(None, description="Code dépôt (DE_Code)")
depot_adresse: Optional[str] = Field(None, description="Adresse (DE_Adresse)")
depot_complement: Optional[str] = Field(None, description="Complément adresse")
depot_code_postal: Optional[str] = Field(None, description="Code postal")
depot_ville: Optional[str] = Field(None, description="Ville")
depot_contact: Optional[str] = Field(None, description="Contact")
depot_est_principal: Optional[bool] = Field(
None, description="Dépôt principal (DE_Principal)"
)
depot_categorie_compta: Optional[int] = Field(
None, description="Catégorie comptable"
)
depot_region: Optional[str] = Field(None, description="Région")
depot_pays: Optional[str] = Field(None, description="Pays")
depot_email: Optional[str] = Field(None, description="Email")
depot_telephone: Optional[str] = Field(None, description="Téléphone")
depot_fax: Optional[str] = Field(None, description="Fax")
depot_emplacement_defaut: Optional[str] = Field(
None, description="Emplacement par défaut"
)
depot_exclu: Optional[bool] = Field(None, description="Dépôt exclu")
emplacement_code: Optional[str] = Field(
None, description="Code emplacement (DP_Code)"
)
emplacement_libelle: Optional[str] = Field(
None, description="Libellé emplacement (DP_Intitule)"
)
emplacement_zone: Optional[str] = Field(None, description="Zone (DP_Zone)")
emplacement_type: Optional[int] = Field(
None, description="Type emplacement (DP_Type)"
)
class Config:
json_schema_extra = {
"example": {
"depot": "01",
"emplacement": "A1-01",
"qte_stockee": 100.0,
"qte_preparee": 5.0,
"depot_nom": "Dépôt principal",
"depot_ville": "Paris",
"emplacement_libelle": "Allée A, Niveau 1, Case 01",
"emplacement_zone": "Zone A",
}
}
class GammeArticleModel(BaseModel):
"""Gamme d'un article (taille, couleur, etc.)"""
numero_gamme: int = Field(..., description="Numéro de gamme (AG_No)")
enumere: str = Field(..., description="Code énuméré (EG_Enumere)")
type_gamme: int = Field(0, description="Type de gamme (AG_Type)")
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
ligne: Optional[int] = Field(None, description="Ligne énuméré (EG_Ligne)")
borne_sup: Optional[float] = Field(
None, description="Borne supérieure (EG_BorneSup)"
)
gamme_nom: Optional[str] = Field(
None, description="Nom de la gamme (P_GAMME.G_Intitule)"
)
class Config:
json_schema_extra = {
"example": {
"numero_gamme": 1,
"enumere": "001",
"type_gamme": 0,
"ligne": 1,
"gamme_nom": "Taille",
}
}
class TarifClientModel(BaseModel):
"""Tarif spécifique pour un client ou catégorie tarifaire"""
categorie: int = Field(..., description="Catégorie tarifaire (AC_Categorie)")
client_num: Optional[str] = Field(None, description="Numéro client (CT_Num)")
prix_vente: float = Field(0.0, description="Prix de vente HT (AC_PrixVen)")
coefficient: float = Field(0.0, description="Coefficient (AC_Coef)")
prix_ttc: float = Field(0.0, description="Prix TTC (AC_PrixTTC)")
arrondi: float = Field(0.0, description="Arrondi (AC_Arrondi)")
qte_montant: float = Field(0.0, description="Quantité montant (AC_QteMont)")
enumere_gamme: int = Field(0, description="Énuméré gamme (EG_Champ)")
prix_devise: float = Field(0.0, description="Prix en devise (AC_PrixDev)")
devise: int = Field(0, description="Code devise (AC_Devise)")
remise: float = Field(0.0, description="Remise (AC_Remise)")
mode_calcul: int = Field(0, description="Mode de calcul (AC_Calcul)")
type_remise: int = Field(0, description="Type de remise (AC_TypeRem)")
ref_client: Optional[str] = Field(
None, description="Référence client (AC_RefClient)"
)
coef_nouveau: float = Field(0.0, description="Nouveau coefficient (AC_CoefNouv)")
prix_vente_nouveau: float = Field(
0.0, description="Nouveau prix vente (AC_PrixVenNouv)"
)
prix_devise_nouveau: float = Field(
0.0, description="Nouveau prix devise (AC_PrixDevNouv)"
)
remise_nouvelle: float = Field(0.0, description="Nouvelle remise (AC_RemiseNouv)")
date_application: Optional[datetime] = Field(
None, description="Date application (AC_DateApplication)"
)
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
class Config:
json_schema_extra = {
"example": {
"categorie": 1,
"client_num": "CLI001",
"prix_vente": 110.00,
"coefficient": 1.294,
"remise": 12.0,
}
}
class ComposantModel(BaseModel):
"""Composant/Opération de nomenclature"""
operation: str = Field(..., description="Code opération (AT_Operation)")
code_ressource: Optional[str] = Field(None, description="Code ressource (RP_Code)")
temps: float = Field(0.0, description="Temps nécessaire (AT_Temps)")
type: int = Field(0, description="Type composant (AT_Type)")
description: Optional[str] = Field(None, description="Description (AT_Description)")
ordre: int = Field(0, description="Ordre d'exécution (AT_Ordre)")
gamme_1_comp: int = Field(0, description="Gamme 1 composant (AG_No1Comp)")
gamme_2_comp: int = Field(0, description="Gamme 2 composant (AG_No2Comp)")
type_ressource: int = Field(0, description="Type ressource (AT_TypeRessource)")
chevauche: int = Field(0, description="Chevauchement (AT_Chevauche)")
demarre: int = Field(0, description="Démarrage (AT_Demarre)")
operation_chevauche: Optional[str] = Field(
None, description="Opération chevauchée (AT_OperationChevauche)"
)
valeur_chevauche: float = Field(
0.0, description="Valeur chevauchement (AT_ValeurChevauche)"
)
type_chevauche: int = Field(0, description="Type chevauchement (AT_TypeChevauche)")
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
class Config:
json_schema_extra = {
"example": {
"operation": "OP010",
"code_ressource": "RES01",
"temps": 15.5,
"description": "Montage pièce A",
"ordre": 10,
}
}
class ComptaArticleModel(BaseModel):
"""Comptabilité spécifique d'un article"""
champ: int = Field(..., description="Champ (ACP_Champ)")
compte_general: Optional[str] = Field(
None, description="Compte général (ACP_ComptaCPT_CompteG)"
)
compte_auxiliaire: Optional[str] = Field(
None, description="Compte auxiliaire (ACP_ComptaCPT_CompteA)"
)
taxe_1: Optional[str] = Field(None, description="Taxe 1 (ACP_ComptaCPT_Taxe1)")
taxe_2: Optional[str] = Field(None, description="Taxe 2 (ACP_ComptaCPT_Taxe2)")
taxe_3: Optional[str] = Field(None, description="Taxe 3 (ACP_ComptaCPT_Taxe3)")
taxe_date_1: Optional[datetime] = Field(None, description="Date taxe 1")
taxe_date_2: Optional[datetime] = Field(None, description="Date taxe 2")
taxe_date_3: Optional[datetime] = Field(None, description="Date taxe 3")
taxe_anc_1: Optional[str] = Field(None, description="Ancienne taxe 1")
taxe_anc_2: Optional[str] = Field(None, description="Ancienne taxe 2")
taxe_anc_3: Optional[str] = Field(None, description="Ancienne taxe 3")
type_facture: int = Field(0, description="Type de facture (ACP_TypeFacture)")
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
class Config:
json_schema_extra = {
"example": {
"champ": 1,
"compte_general": "707100",
"taxe_1": "TVA20",
"type_facture": 0,
}
}
class FournisseurArticleModel(BaseModel):
"""Fournisseur d'un article"""
fournisseur_num: str = Field(..., description="Numéro fournisseur (CT_Num)")
ref_fournisseur: Optional[str] = Field(
None, description="Référence fournisseur (AF_RefFourniss)"
)
prix_achat: float = Field(0.0, description="Prix d'achat (AF_PrixAch)")
unite: Optional[str] = Field(None, description="Unité (AF_Unite)")
conversion: float = Field(0.0, description="Conversion (AF_Conversion)")
delai_appro: int = Field(0, description="Délai approvisionnement (AF_DelaiAppro)")
garantie: int = Field(0, description="Garantie (AF_Garantie)")
colisage: int = Field(0, description="Colisage (AF_Colisage)")
qte_mini: float = Field(0.0, description="Quantité minimum (AF_QteMini)")
qte_montant: float = Field(0.0, description="Quantité montant (AF_QteMont)")
enumere_gamme: int = Field(0, description="Énuméré gamme (EG_Champ)")
est_principal: bool = Field(
False, description="Fournisseur principal (AF_Principal)"
)
prix_devise: float = Field(0.0, description="Prix devise (AF_PrixDev)")
devise: int = Field(0, description="Code devise (AF_Devise)")
remise: float = Field(0.0, description="Remise (AF_Remise)")
conversion_devise: float = Field(0.0, description="Conversion devise (AF_ConvDiv)")
type_remise: int = Field(0, description="Type remise (AF_TypeRem)")
code_barre_fournisseur: Optional[str] = Field(
None, description="Code-barres fournisseur (AF_CodeBarre)"
)
prix_achat_nouveau: float = Field(
0.0, description="Nouveau prix achat (AF_PrixAchNouv)"
)
prix_devise_nouveau: float = Field(
0.0, description="Nouveau prix devise (AF_PrixDevNouv)"
)
remise_nouvelle: float = Field(0.0, description="Nouvelle remise (AF_RemiseNouv)")
date_application: Optional[datetime] = Field(
None, description="Date application (AF_DateApplication)"
)
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
class Config:
json_schema_extra = {
"example": {
"fournisseur_num": "F001",
"ref_fournisseur": "REF-FOURN-001",
"prix_achat": 85.00,
"delai_appro": 15,
"est_principal": True,
}
}
class ReferenceEnumereeModel(BaseModel):
"""Référence énumérée (article avec gammes)"""
gamme_1: int = Field(0, description="Gamme 1 (AG_No1)")
gamme_2: int = Field(0, description="Gamme 2 (AG_No2)")
reference_enumeree: str = Field(..., description="Référence énumérée (AE_Ref)")
prix_achat: float = Field(0.0, description="Prix achat (AE_PrixAch)")
code_barre: Optional[str] = Field(None, description="Code-barres (AE_CodeBarre)")
prix_achat_nouveau: float = Field(
0.0, description="Nouveau prix achat (AE_PrixAchNouv)"
)
edi_code: Optional[str] = Field(None, description="Code EDI (AE_EdiCode)")
en_sommeil: bool = Field(False, description="En sommeil (AE_Sommeil)")
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
class Config:
json_schema_extra = {
"example": {
"gamme_1": 1,
"gamme_2": 3,
"reference_enumeree": "ART001-T1-C3",
"prix_achat": 85.00,
}
}
class MediaArticleModel(BaseModel):
"""Média attaché à un article (photo, document, etc.)"""
commentaire: Optional[str] = Field(None, description="Commentaire (ME_Commentaire)")
fichier: Optional[str] = Field(None, description="Nom fichier (ME_Fichier)")
type_mime: Optional[str] = Field(None, description="Type MIME (ME_TypeMIME)")
origine: int = Field(0, description="Origine (ME_Origine)")
ged_id: Optional[str] = Field(None, description="ID GED (ME_GedId)")
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
class Config:
json_schema_extra = {
"example": {
"commentaire": "Photo produit principale",
"fichier": "ART001_photo1.jpg",
"type_mime": "image/jpeg",
}
}
class PrixGammeModel(BaseModel):
"""Prix spécifique par combinaison de gammes"""
gamme_1: int = Field(0, description="Gamme 1 (AG_No1)")
gamme_2: int = Field(0, description="Gamme 2 (AG_No2)")
prix_net: float = Field(0.0, description="Prix net (AR_PUNet)")
cout_standard: float = Field(0.0, description="Coût standard (AR_CoutStd)")
date_creation: Optional[datetime] = Field(None, description="Date création")
date_modification: Optional[datetime] = Field(None, description="Date modification")
class Config:
json_schema_extra = {
"example": {
"gamme_1": 1,
"gamme_2": 3,
"prix_net": 125.50,
"cout_standard": 82.30,
}
}
class ArticleResponse(BaseModel):
"""Article complet avec tous les enrichissements disponibles"""
reference: str = Field(..., description="Référence article (AR_Ref)")
designation: str = Field(..., description="Désignation principale (AR_Design)")
code_ean: Optional[str] = Field(
None, description="Code EAN / Code-barres principal (AR_CodeBarre)"
)
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)")
prix_vente: float = Field(..., description="Prix de vente HT unitaire (AR_PrixVen)")
prix_achat: Optional[float] = Field(
None, description="Prix d'achat HT (AR_PrixAch)"
)
coef: Optional[float] = Field(
None, description="Coefficient multiplicateur (AR_Coef)"
)
prix_net: Optional[float] = Field(None, description="Prix unitaire net (AR_PUNet)")
prix_achat_nouveau: Optional[float] = Field(
None, description="Nouveau prix d'achat à venir (AR_PrixAchNouv)"
)
coef_nouveau: Optional[float] = Field(
None, description="Nouveau coefficient à venir (AR_CoefNouv)"
)
prix_vente_nouveau: Optional[float] = Field(
None, 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, description="Coût standard (AR_CoutStd)"
)
stock_reel: float = Field(
default=0.0, description="Stock réel total (F_ARTSTOCK.AS_QteSto)"
)
stock_mini: Optional[float] = Field(
None, description="Stock minimum (F_ARTSTOCK.AS_QteMini)"
)
stock_maxi: Optional[float] = Field(
None, description="Stock maximum (F_ARTSTOCK.AS_QteMaxi)"
)
stock_reserve: Optional[float] = Field(
None, description="Stock réservé / en commande client (F_ARTSTOCK.AS_QteRes)"
)
stock_commande: Optional[float] = Field(
None, description="Stock en commande fournisseur (F_ARTSTOCK.AS_QteCom)"
)
stock_disponible: Optional[float] = Field(
None, description="Stock disponible = réel - réservé"
)
emplacements: List[dict] = Field(
default_factory=list, description="Détail du stock par emplacement"
)
nb_emplacements: int = Field(0, description="Nombre d'emplacements")
# Champs énumérés normalisés
suivi_stock: Optional[int] = Field(
None,
description="Type de suivi de stock (AR_SuiviStock): 0=Aucun, 1=CMUP, 2=FIFO/LIFO, 3=Sérialisé",
)
suivi_stock_libelle: Optional[str] = Field(
None, description="Libellé du type de suivi de stock"
)
nomenclature: Optional[int] = Field(
None,
description="Type de nomenclature (AR_Nomencl): 0=Non, 1=Fabrication, 2=Commerciale",
)
nomenclature_libelle: Optional[str] = Field(
None, description="Libellé du type de nomenclature"
)
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_poids: Optional[str] = Field(
None, max_length=10, description="Unité de poids (AR_UnitePoids)"
)
poids_net: Optional[float] = Field(
None, description="Poids net unitaire en kg (AR_PoidsNet)"
)
poids_brut: Optional[float] = Field(
None, description="Poids brut unitaire en kg (AR_PoidsBrut)"
)
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)")
gammes: List[dict] = Field(default_factory=list, description="Détail des gammes")
nb_gammes: int = Field(0, description="Nombre de gammes")
tarifs_clients: List[dict] = Field(
default_factory=list, description="Tarifs spécifiques par client/catégorie"
)
nb_tarifs_clients: int = Field(0, description="Nombre de tarifs clients")
composants: List[dict] = Field(
default_factory=list, description="Composants/Opérations de production"
)
nb_composants: int = Field(0, description="Nombre de composants")
compta_vente: List[dict] = Field(
default_factory=list, description="Comptabilité vente"
)
compta_achat: List[dict] = Field(
default_factory=list, description="Comptabilité achat"
)
compta_stock: List[dict] = Field(
default_factory=list, description="Comptabilité stock"
)
fournisseurs: List[dict] = Field(
default_factory=list, description="Tous les fournisseurs de l'article"
)
nb_fournisseurs: int = Field(0, description="Nombre de fournisseurs")
refs_enumerees: List[dict] = Field(
default_factory=list, description="Références énumérées"
)
nb_refs_enumerees: int = Field(0, description="Nombre de références énumérées")
medias: List[dict] = Field(default_factory=list, description="Médias attachés")
nb_medias: int = Field(0, description="Nombre de médias")
prix_gammes: List[dict] = Field(
default_factory=list, description="Prix par combinaison de gammes"
)
nb_prix_gammes: int = Field(0, description="Nombre de prix par gammes")
type_article: Optional[int] = Field(
None,
ge=0,
le=3,
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"
)
famille_code: Optional[str] = Field(
None, max_length=20, description="Code famille (FA_CodeFamille)"
)
famille_libelle: Optional[str] = Field(None, description="Libellé de la famille")
famille_type: Optional[int] = Field(
None, description="Type de famille : 0=Détail, 1=Total"
)
famille_unite_vente: Optional[str] = Field(
None, description="Unité de vente de la famille"
)
famille_coef: Optional[float] = Field(None, description="Coefficient de la famille")
famille_suivi_stock: Optional[bool] = Field(
None, description="Suivi stock de la famille"
)
famille_garantie: Optional[int] = Field(None, description="Garantie de la famille")
famille_unite_poids: Optional[str] = Field(
None, description="Unité de poids de la famille"
)
famille_delai: Optional[int] = Field(None, description="Délai de la famille")
famille_nb_colis: Optional[int] = Field(
None, description="Nombre de colis de la famille"
)
famille_code_fiscal: Optional[str] = Field(
None, description="Code fiscal de la famille"
)
famille_escompte: Optional[bool] = Field(None, description="Escompte de la famille")
famille_centrale: Optional[bool] = Field(None, description="Famille centrale")
famille_nature: Optional[int] = Field(None, description="Nature de la famille")
famille_hors_stat: Optional[bool] = Field(
None, description="Hors statistique famille"
)
famille_pays: Optional[str] = Field(None, description="Pays de la famille")
nature: Optional[int] = Field(None, description="Nature de l'article (AR_Nature)")
garantie: Optional[int] = Field(
None, 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"
)
fournisseur_nom: Optional[str] = Field(
None, description="Nom du fournisseur principal"
)
conditionnement: Optional[str] = Field(
None, description="Conditionnement d'achat (AR_Condition)"
)
conditionnement_qte: Optional[float] = Field(
None, description="Quantité conditionnement"
)
conditionnement_edi: Optional[str] = Field(
None, description="Code EDI conditionnement"
)
nb_colis: Optional[int] = Field(
None, description="Nombre de colis par unité (AR_NbColis)"
)
prevision: Optional[bool] = Field(
None, description="Gestion en prévision (AR_Prevision)"
)
est_actif: bool = Field(default=True, description="Article actif (AR_Sommeil = 0)")
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)"
)
hors_statistique: Optional[bool] = Field(
None, description="Exclus des statistiques (AR_HorsStat)"
)
vente_debit: Optional[bool] = Field(
None, description="Vente au débit (AR_VteDebit)"
)
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)")
sous_traitance: Optional[bool] = Field(
None, description="Article en sous-traitance (AR_SousTraitance)"
)
criticite: Optional[int] = Field(
None, description="Niveau de criticité (AR_Criticite)"
)
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"
)
frais_02_denomination: Optional[str] = Field(
None, description="Dénomination frais 2"
)
frais_03_denomination: Optional[str] = Field(
None, description="Dénomination frais 3"
)
tva_code: Optional[str] = Field(None, description="Code TVA (F_TAXE.TA_Code)")
tva_taux: Optional[float] = Field(
None, description="Taux de TVA en % (F_TAXE.TA_Taux)"
)
stat_01: Optional[str] = Field(None, description="Statistique 1 (AR_Stat01)")
stat_02: Optional[str] = Field(None, description="Statistique 2 (AR_Stat02)")
stat_03: Optional[str] = Field(None, description="Statistique 3 (AR_Stat03)")
stat_04: Optional[str] = Field(None, description="Statistique 4 (AR_Stat04)")
stat_05: Optional[str] = Field(None, description="Statistique 5 (AR_Stat05)")
categorie_1: Optional[int] = Field(
None, description="Catégorie comptable 1 (CL_No1)"
)
categorie_2: Optional[int] = Field(
None, description="Catégorie comptable 2 (CL_No2)"
)
categorie_3: Optional[int] = Field(
None, description="Catégorie comptable 3 (CL_No3)"
)
categorie_4: Optional[int] = Field(
None, description="Catégorie comptable 4 (CL_No4)"
)
date_modification: Optional[str] = Field(
None, description="Date de dernière modification (AR_DateModif)"
)
marque_commerciale: Optional[str] = Field(None, description="Marque commerciale")
objectif_qtes_vendues: Optional[str] = Field(
None, description="Objectif / Quantités vendues"
)
pourcentage_or: Optional[str] = Field(None, description="Pourcentage teneur en or")
premiere_commercialisation: Optional[str] = Field(
None, description="Date de 1ère commercialisation"
)
interdire_commande: Optional[bool] = Field(
None, description="Interdire la commande"
)
exclure: Optional[bool] = Field(None, description="Exclure de certains traitements")
# ===== VALIDATEURS =====
@field_validator(
"unite_vente",
"unite_poids",
"gamme_1",
"gamme_2",
"conditionnement",
"code_fiscal",
"pays",
"article_substitut",
"reprise_code_defaut",
mode="before",
)
@classmethod
def convert_string_fields(cls, v):
"""Convertit les champs string qui peuvent venir comme int depuis la DB"""
return normalize_string_field(v)
@field_validator("suivi_stock", "nomenclature", mode="before")
@classmethod
def convert_enum_fields(cls, v):
"""Convertit les champs énumérés en int"""
return normalize_enum_to_int(v)
def model_post_init(self, __context):
"""Génère automatiquement les libellés après l'initialisation"""
if self.suivi_stock is not None:
self.suivi_stock_libelle = SuiviStockType.get_label(self.suivi_stock)
if self.nomenclature is not None:
self.nomenclature_libelle = NomenclatureType.get_label(self.nomenclature)
if self.type_article is not None:
self.type_article_libelle = TypeArticle.get_label(self.type_article)
class Config:
json_schema_extra = {
"example": {
"reference": "BAGUE-001",
"designation": "Bague Or 18K Diamant",
"prix_vente": 1299.00,
"stock_reel": 15.0,
"suivi_stock": 1,
"suivi_stock_libelle": "CMUP",
"nomenclature": 0,
"nomenclature_libelle": "Non",
}
}
class ArticleListResponse(BaseModel):
"""Réponse pour une liste d'articles"""
total: int = Field(..., description="Nombre total d'articles")
articles: List[ArticleResponse] = Field(..., description="Liste des articles")
filtre_applique: Optional[str] = Field(
None, description="Filtre de recherche appliqué"
)
avec_stock: bool = Field(True, description="Indique si les stocks ont été chargés")
avec_famille: bool = Field(
True, description="Indique si les familles ont été enrichies"
)
avec_enrichissements_complets: bool = Field(
False, description="Indique si tous les enrichissements sont activés"
)
class ArticleCreateRequest(BaseModel):
"""Schéma pour création d'article"""
reference: str = Field(..., max_length=18, description="Référence article")
designation: str = Field(..., max_length=69, description="Désignation")
famille: Optional[str] = Field(None, max_length=18, description="Code famille")
prix_vente: Optional[float] = Field(None, ge=0, description="Prix vente HT")
prix_achat: Optional[float] = Field(None, ge=0, description="Prix achat HT")
stock_reel: Optional[float] = Field(None, ge=0, description="Stock initial")
stock_mini: Optional[float] = Field(None, ge=0, description="Stock minimum")
code_ean: Optional[str] = Field(None, max_length=13, description="Code-barres")
unite_vente: Optional[str] = Field("UN", max_length=4, description="Unité")
tva_code: Optional[str] = Field(None, max_length=5, description="Code TVA")
description: Optional[str] = Field(None, description="Description")
class ArticleUpdateRequest(BaseModel):
"""Schéma pour modification d'article"""
designation: Optional[str] = Field(None, max_length=69)
prix_vente: Optional[float] = Field(None, ge=0)
prix_achat: Optional[float] = Field(None, ge=0)
stock_reel: Optional[float] = Field(
None, ge=0, description="Critique pour erreur 2881"
)
stock_mini: Optional[float] = Field(None, ge=0)
code_ean: Optional[str] = Field(None, max_length=13)
description: Optional[str] = Field(None)
class MouvementStockLigneRequest(BaseModel):
article_ref: str = Field(..., description="Référence de l'article")
quantite: float = Field(..., gt=0, description="Quantité (>0)")
depot_code: Optional[str] = Field(None, description="Code du dépôt (ex: '01')")
prix_unitaire: Optional[float] = Field(
None, ge=0, description="Prix unitaire (optionnel)"
)
commentaire: Optional[str] = Field(None, description="Commentaire ligne")
numero_lot: Optional[str] = Field(
None, description="Numéro de lot (pour FIFO/LIFO)"
)
stock_mini: Optional[float] = Field(
None,
ge=0,
description="""Stock minimum à définir pour cet article.
Si fourni, met à jour AS_QteMini dans F_ARTSTOCK.
Laisser None pour ne pas modifier.""",
)
stock_maxi: Optional[float] = Field(
None,
ge=0,
description="""Stock maximum à définir pour cet article.
Doit être > stock_mini si les deux sont fournis.""",
)
class Config:
json_schema_extra = {
"example": {
"article_ref": "ARTS-001",
"quantite": 50.0,
"depot_code": "01",
"prix_unitaire": 100.0,
"commentaire": "Réapprovisionnement",
"numero_lot": "LOT20241217",
"stock_mini": 10.0,
"stock_maxi": 200.0,
}
}
@validator("stock_maxi")
def validate_stock_maxi(cls, v, values):
"""Valide que stock_maxi > stock_mini si les deux sont fournis"""
if (
v is not None
and "stock_mini" in values
and values["stock_mini"] is not None
):
if v <= values["stock_mini"]:
raise ValueError(
"stock_maxi doit être strictement supérieur à stock_mini"
)
return v
class EntreeStockRequest(BaseModel):
"""Création d'un bon d'entrée en stock"""
date_entree: Optional[date] = Field(
None, description="Date du mouvement (aujourd'hui par défaut)"
)
reference: Optional[str] = Field(None, description="Référence externe")
depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)"
)
lignes: List[MouvementStockLigneRequest] = Field(
..., min_items=1, description="Lignes du mouvement"
)
commentaire: Optional[str] = Field(None, description="Commentaire général")
class Config:
json_schema_extra = {
"example": {
"date_entree": "2025-01-15",
"reference": "REC-2025-001",
"depot_code": "01",
"lignes": [
{
"article_ref": "ART001",
"quantite": 50,
"depot_code": "01",
"prix_unitaire": 10.50,
"commentaire": "Réception fournisseur",
}
],
"commentaire": "Réception livraison fournisseur XYZ",
}
}
class SortieStockRequest(BaseModel):
"""Création d'un bon de sortie de stock"""
date_sortie: Optional[date] = Field(
None, description="Date du mouvement (aujourd'hui par défaut)"
)
reference: Optional[str] = Field(None, description="Référence externe")
depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)"
)
lignes: List[MouvementStockLigneRequest] = Field(
..., min_items=1, description="Lignes du mouvement"
)
commentaire: Optional[str] = Field(None, description="Commentaire général")
class Config:
json_schema_extra = {
"example": {
"date_sortie": "2025-01-15",
"reference": "SOR-2025-001",
"depot_code": "01",
"lignes": [
{
"article_ref": "ART001",
"quantite": 10,
"depot_code": "01",
"commentaire": "Utilisation interne",
}
],
"commentaire": "Consommation atelier",
}
}
class MouvementStockResponse(BaseModel):
"""Réponse pour un mouvement de stock"""
article_ref: str = Field(..., description="Numéro d'article")
numero: str = Field(..., description="Numéro du mouvement")
type: int = Field(..., description="Type (0=Entrée, 1=Sortie)")
type_libelle: str = Field(..., description="Libellé du type")
date: str = Field(..., description="Date du mouvement")
reference: Optional[str] = Field(None, description="Référence externe")
nb_lignes: int = Field(..., description="Nombre de lignes")