refactor(schemas): rename response and request models to simpler names

This commit is contained in:
Fanilo-Nantenaina 2026-01-03 11:11:28 +03:00
parent e3b0f7e44a
commit 306c71b43d
16 changed files with 194 additions and 553 deletions

206
api.py
View file

@ -23,10 +23,10 @@ from database import (
async_session_factory, async_session_factory,
get_session, get_session,
EmailLog, EmailLog,
StatutEmail as StatutEmailEnum, StatutEmail as StatutEmailDB,
WorkflowLog, WorkflowLog,
SignatureLog, SignatureLog,
StatutSignature as StatutSignatureEnum, StatutSignature as StatutSignatureDB,
) )
from email_queue import email_queue from email_queue import email_queue
from sage_client import sage_client, SageGatewayClient from sage_client import sage_client, SageGatewayClient
@ -34,45 +34,44 @@ from sage_client import sage_client, SageGatewayClient
from schemas import ( from schemas import (
TiersDetails, TiersDetails,
BaremeRemiseResponse, BaremeRemiseResponse,
UserResponse, Users,
ClientCreateRequest, ClientCreate,
ClientDetails, ClientDetails,
ClientUpdateRequest, ClientUpdate,
FournisseurCreateAPIRequest, FournisseurCreate,
FournisseurDetails, FournisseurDetails,
FournisseurUpdateRequest, FournisseurUpdate,
Contact, Contact,
AvoirCreateRequest, AvoirCreate,
AvoirUpdateRequest, AvoirUpdate,
CommandeCreateRequest, CommandeCreate,
CommandeUpdateRequest, CommandeUpdate,
DevisRequest, DevisRequest,
DevisResponse, Devis,
DevisUpdateRequest, DevisUpdate,
TypeDocument, TypeDocument,
TypeDocumentSQL, TypeDocumentSQL,
StatutEmail, StatutEmail,
EmailEnvoiRequest, EmailEnvoi,
FactureCreateRequest, FactureCreate,
FactureUpdateRequest, FactureUpdate,
LivraisonCreateRequest, LivraisonCreate,
LivraisonUpdateRequest, LivraisonUpdate,
SignatureRequest, Signature,
StatutSignature, StatutSignature,
ArticleCreateRequest, ArticleCreate,
ArticleResponse, Article,
ArticleUpdateRequest, ArticleUpdate,
EntreeStockRequest, EntreeStock,
SortieStockRequest, SortieStock,
MouvementStockResponse, MouvementStock,
RelanceDevisRequest, RelanceDevis,
FamilleResponse, Familles,
FamilleCreateRequest, FamilleCreate,
ContactCreate, ContactCreate,
ContactUpdate, ContactUpdate,
) )
from utils.normalization import normaliser_type_tiers from utils.normalization import normaliser_type_tiers
from routes.sage_gateway import router as sage_gateway_router from routes.sage_gateway import router as sage_gateway_router
from core.sage_context import ( from core.sage_context import (
get_sage_client_for_user, get_sage_client_for_user,
@ -80,6 +79,7 @@ from core.sage_context import (
GatewayContext, GatewayContext,
) )
if os.path.exists("/app"): if os.path.exists("/app"):
LOGS_DIR = FilePath("/app/logs") LOGS_DIR = FilePath("/app/logs")
else: else:
@ -264,7 +264,7 @@ async def universign_envoyer(
corps_html=corps, corps_html=corps,
document_ids=doc_id, document_ids=doc_id,
type_document=doc_data.get("type_doc"), type_document=doc_data.get("type_doc"),
statut=StatutEmailEnum.EN_ATTENTE, statut=StatutEmailDB.EN_ATTENTE,
date_creation=datetime.now(), date_creation=datetime.now(),
nb_tentatives=0, nb_tentatives=0,
) )
@ -347,7 +347,7 @@ async def lire_client_detail(code: str):
@app.put("/clients/{code}", tags=["Clients"]) @app.put("/clients/{code}", tags=["Clients"])
async def modifier_client( async def modifier_client(
code: str, code: str,
client_update: ClientUpdateRequest, client_update: ClientUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -373,7 +373,7 @@ async def modifier_client(
@app.post("/clients", status_code=201, tags=["Clients"]) @app.post("/clients", status_code=201, tags=["Clients"])
async def ajouter_client( async def ajouter_client(
client: ClientCreateRequest, session: AsyncSession = Depends(get_session) client: ClientCreate, session: AsyncSession = Depends(get_session)
): ):
try: try:
nouveau_client = sage_client.creer_client(client.model_dump(mode="json")) nouveau_client = sage_client.creer_client(client.model_dump(mode="json"))
@ -394,11 +394,11 @@ async def ajouter_client(
raise HTTPException(status, str(e)) raise HTTPException(status, str(e))
@app.get("/articles", response_model=List[ArticleResponse], tags=["Articles"]) @app.get("/articles", response_model=List[Article], tags=["Articles"])
async def rechercher_articles(query: Optional[str] = Query(None)): async def rechercher_articles(query: Optional[str] = Query(None)):
try: try:
articles = sage_client.lister_articles(filtre=query or "") articles = sage_client.lister_articles(filtre=query or "")
return [ArticleResponse(**a) for a in articles] return [Article(**a) for a in articles]
except Exception as e: except Exception as e:
logger.error(f"Erreur recherche articles: {e}") logger.error(f"Erreur recherche articles: {e}")
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@ -406,11 +406,11 @@ async def rechercher_articles(query: Optional[str] = Query(None)):
@app.post( @app.post(
"/articles", "/articles",
response_model=ArticleResponse, response_model=Article,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
tags=["Articles"], tags=["Articles"],
) )
async def creer_article(article: ArticleCreateRequest): async def creer_article(article: ArticleCreate):
try: try:
if not article.reference or not article.designation: if not article.reference or not article.designation:
raise HTTPException( raise HTTPException(
@ -428,7 +428,7 @@ async def creer_article(article: ArticleCreateRequest):
f"Article créé: {resultat.get('reference')} (stock: {resultat.get('stock_reel', 0)})" f"Article créé: {resultat.get('reference')} (stock: {resultat.get('stock_reel', 0)})"
) )
return ArticleResponse(**resultat) return Article(**resultat)
except ValueError as e: except ValueError as e:
logger.warning(f"Erreur métier création article: {e}") logger.warning(f"Erreur métier création article: {e}")
@ -445,10 +445,10 @@ async def creer_article(article: ArticleCreateRequest):
) )
@app.put("/articles/{reference}", response_model=ArticleResponse, tags=["Articles"]) @app.put("/articles/{reference}", response_model=Article, tags=["Articles"])
async def modifier_article( async def modifier_article(
reference: str = Path(..., description="Référence de l'article à modifier"), reference: str = Path(..., description="Référence de l'article à modifier"),
article: ArticleUpdateRequest = Body(...), article: ArticleUpdate = Body(...),
): ):
try: try:
article_data = article.dict(exclude_unset=True) article_data = article.dict(exclude_unset=True)
@ -471,7 +471,7 @@ async def modifier_article(
logger.info(f"Article {reference} modifié ({len(article_data)} champs)") logger.info(f"Article {reference} modifié ({len(article_data)} champs)")
return ArticleResponse(**resultat) return Article(**resultat)
except ValueError as e: except ValueError as e:
logger.warning(f"Erreur métier modification article: {e}") logger.warning(f"Erreur métier modification article: {e}")
@ -488,7 +488,7 @@ async def modifier_article(
) )
@app.get("/articles/{reference}", response_model=ArticleResponse, tags=["Articles"]) @app.get("/articles/{reference}", response_model=Article, tags=["Articles"])
async def lire_article( async def lire_article(
reference: str = Path(..., description="Référence de l'article"), reference: str = Path(..., description="Référence de l'article"),
): ):
@ -504,7 +504,7 @@ async def lire_article(
logger.info(f"Article {reference} lu: {article.get('designation', '')}") logger.info(f"Article {reference} lu: {article.get('designation', '')}")
return ArticleResponse(**article) return Article(**article)
except HTTPException: except HTTPException:
raise raise
@ -516,7 +516,7 @@ async def lire_article(
) )
@app.post("/devis", response_model=DevisResponse, status_code=201, tags=["Devis"]) @app.post("/devis", response_model=Devis, status_code=201, tags=["Devis"])
async def creer_devis(devis: DevisRequest): async def creer_devis(devis: DevisRequest):
try: try:
devis_data = { devis_data = {
@ -540,7 +540,7 @@ async def creer_devis(devis: DevisRequest):
logger.info(f"Devis créé: {resultat.get('numero_devis')}") logger.info(f"Devis créé: {resultat.get('numero_devis')}")
return DevisResponse( return Devis(
id=resultat["numero_devis"], id=resultat["numero_devis"],
client_id=devis.client_id, client_id=devis.client_id,
date_devis=resultat["date_devis"], date_devis=resultat["date_devis"],
@ -557,7 +557,7 @@ async def creer_devis(devis: DevisRequest):
@app.put("/devis/{id}", tags=["Devis"]) @app.put("/devis/{id}", tags=["Devis"])
async def modifier_devis( async def modifier_devis(
id: str, id: str,
devis_update: DevisUpdateRequest, devis_update: DevisUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -601,7 +601,7 @@ async def modifier_devis(
@app.post("/commandes", status_code=201, tags=["Commandes"]) @app.post("/commandes", status_code=201, tags=["Commandes"])
async def creer_commande( async def creer_commande(
commande: CommandeCreateRequest, session: AsyncSession = Depends(get_session) commande: CommandeCreate, session: AsyncSession = Depends(get_session)
): ):
try: try:
commande_data = { commande_data = {
@ -651,7 +651,7 @@ async def creer_commande(
@app.put("/commandes/{id}", tags=["Commandes"]) @app.put("/commandes/{id}", tags=["Commandes"])
async def modifier_commande( async def modifier_commande(
id: str, id: str,
commande_update: CommandeUpdateRequest, commande_update: CommandeUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -803,7 +803,7 @@ async def telecharger_document_pdf(
@app.post("/devis/{id}/envoyer", tags=["Devis"]) @app.post("/devis/{id}/envoyer", tags=["Devis"])
async def envoyer_devis_email( async def envoyer_devis_email(
id: str, request: EmailEnvoiRequest, session: AsyncSession = Depends(get_session) id: str, request: EmailEnvoi, session: AsyncSession = Depends(get_session)
): ):
try: try:
tous_destinataires = [request.destinataire] + request.cc + request.cci tous_destinataires = [request.destinataire] + request.cc + request.cci
@ -817,7 +817,7 @@ async def envoyer_devis_email(
corps_html=request.corps_html, corps_html=request.corps_html,
document_ids=id, document_ids=id,
type_document=TypeDocument.DEVIS, type_document=TypeDocument.DEVIS,
statut=StatutEmailEnum.EN_ATTENTE, statut=StatutEmailDB.EN_ATTENTE,
date_creation=datetime.now(), date_creation=datetime.now(),
nb_tentatives=0, nb_tentatives=0,
) )
@ -1089,7 +1089,7 @@ def normaliser_type_doc(type_doc: int) -> int:
@app.post("/signature/universign/send", tags=["Signatures"]) @app.post("/signature/universign/send", tags=["Signatures"])
async def envoyer_signature_optimise( async def envoyer_signature_optimise(
demande: SignatureRequest, session: AsyncSession = Depends(get_session) demande: Signature, session: AsyncSession = Depends(get_session)
): ):
try: try:
doc = sage_client.lire_document( doc = sage_client.lire_document(
@ -1135,7 +1135,7 @@ async def envoyer_signature_optimise(
signer_url=resultat["signer_url"], signer_url=resultat["signer_url"],
email_signataire=demande.email_signataire, email_signataire=demande.email_signataire,
nom_signataire=demande.nom_signataire, nom_signataire=demande.nom_signataire,
statut=StatutSignatureEnum.ENVOYE, statut=StatutSignatureDB.ENVOYE,
date_envoi=datetime.now(), date_envoi=datetime.now(),
) )
@ -1191,7 +1191,7 @@ async def webhook_universign(
return {"status": "not_found"} return {"status": "not_found"}
if event_type == "transaction.completed": if event_type == "transaction.completed":
signature_log.statut = StatutSignatureEnum.SIGNE signature_log.statut = StatutSignatureDB.SIGNE
signature_log.date_signature = datetime.now() signature_log.date_signature = datetime.now()
logger.info(f"Signature confirmée: {signature_log.document_id}") logger.info(f"Signature confirmée: {signature_log.document_id}")
@ -1229,7 +1229,7 @@ async def webhook_universign(
corps_html=corps, corps_html=corps,
document_ids=signature_log.document_id, document_ids=signature_log.document_id,
type_document=signature_log.type_document, type_document=signature_log.type_document,
statut=StatutEmailEnum.EN_ATTENTE, statut=StatutEmailDB.EN_ATTENTE,
date_creation=datetime.now(), date_creation=datetime.now(),
nb_tentatives=0, nb_tentatives=0,
) )
@ -1242,11 +1242,11 @@ async def webhook_universign(
) )
elif event_type == "transaction.refused": elif event_type == "transaction.refused":
signature_log.statut = StatutSignatureEnum.REFUSE signature_log.statut = StatutSignatureDB.REFUSE
logger.warning(f"Signature refusée: {signature_log.document_id}") logger.warning(f"Signature refusée: {signature_log.document_id}")
elif event_type == "transaction.expired": elif event_type == "transaction.expired":
signature_log.statut = StatutSignatureEnum.EXPIRE signature_log.statut = StatutSignatureDB.EXPIRE
logger.warning(f"⏰ Transaction expirée: {signature_log.document_id}") logger.warning(f"⏰ Transaction expirée: {signature_log.document_id}")
await session.commit() await session.commit()
@ -1271,7 +1271,7 @@ async def relancer_signatures_automatique(session: AsyncSession = Depends(get_se
query = select(SignatureLog).where( query = select(SignatureLog).where(
SignatureLog.statut.in_( SignatureLog.statut.in_(
[StatutSignatureEnum.EN_ATTENTE, StatutSignatureEnum.ENVOYE] [StatutSignatureDB.EN_ATTENTE, StatutSignatureDB.ENVOYE]
), ),
SignatureLog.date_envoi < date_limite, SignatureLog.date_envoi < date_limite,
SignatureLog.nb_relances < 3, # Max 3 relances SignatureLog.nb_relances < 3, # Max 3 relances
@ -1288,7 +1288,7 @@ async def relancer_signatures_automatique(session: AsyncSession = Depends(get_se
jours_restants = 30 - nb_jours # Lien expire après 30 jours jours_restants = 30 - nb_jours # Lien expire après 30 jours
if jours_restants <= 0: if jours_restants <= 0:
signature.statut = StatutSignatureEnum.EXPIRE signature.statut = StatutSignatureDB.EXPIRE
continue continue
template = templates_signature_email["relance_signature"] template = templates_signature_email["relance_signature"]
@ -1325,7 +1325,7 @@ async def relancer_signatures_automatique(session: AsyncSession = Depends(get_se
corps_html=corps, corps_html=corps,
document_ids=signature.document_id, document_ids=signature.document_id,
type_document=signature.type_document, type_document=signature.type_document,
statut=StatutEmailEnum.EN_ATTENTE, statut=StatutEmailDB.EN_ATTENTE,
date_creation=datetime.now(), date_creation=datetime.now(),
nb_tentatives=0, nb_tentatives=0,
) )
@ -1394,7 +1394,7 @@ async def lister_signatures(
query = select(SignatureLog).order_by(SignatureLog.date_envoi.desc()) query = select(SignatureLog).order_by(SignatureLog.date_envoi.desc())
if statut: if statut:
statut_db = StatutSignatureEnum[statut.value] statut_db = StatutSignatureDB[statut.value]
query = query.where(SignatureLog.statut == statut_db) query = query.where(SignatureLog.statut == statut_db)
query = query.limit(limit) query = query.limit(limit)
@ -1437,15 +1437,15 @@ async def statut_signature_detail(
if statut_universign.get("statut") != "ERREUR": if statut_universign.get("statut") != "ERREUR":
statut_map = { statut_map = {
"EN_ATTENTE": StatutSignatureEnum.EN_ATTENTE, "EN_ATTENTE": StatutSignatureDB.EN_ATTENTE,
"ENVOYE": StatutSignatureEnum.ENVOYE, "ENVOYE": StatutSignatureDB.ENVOYE,
"SIGNE": StatutSignatureEnum.SIGNE, "SIGNE": StatutSignatureDB.SIGNE,
"REFUSE": StatutSignatureEnum.REFUSE, "REFUSE": StatutSignatureDB.REFUSE,
"EXPIRE": StatutSignatureEnum.EXPIRE, "EXPIRE": StatutSignatureDB.EXPIRE,
} }
nouveau_statut = statut_map.get( nouveau_statut = statut_map.get(
statut_universign["statut"], StatutSignatureEnum.EN_ATTENTE statut_universign["statut"], StatutSignatureDB.EN_ATTENTE
) )
signature_log.statut = nouveau_statut signature_log.statut = nouveau_statut
@ -1478,7 +1478,7 @@ async def statut_signature_detail(
async def rafraichir_statuts_signatures(session: AsyncSession = Depends(get_session)): async def rafraichir_statuts_signatures(session: AsyncSession = Depends(get_session)):
query = select(SignatureLog).where( query = select(SignatureLog).where(
SignatureLog.statut.in_( SignatureLog.statut.in_(
[StatutSignatureEnum.EN_ATTENTE, StatutSignatureEnum.ENVOYE] [StatutSignatureDB.EN_ATTENTE, StatutSignatureDB.ENVOYE]
) )
) )
@ -1492,9 +1492,9 @@ async def rafraichir_statuts_signatures(session: AsyncSession = Depends(get_sess
if statut_universign.get("statut") != "ERREUR": if statut_universign.get("statut") != "ERREUR":
statut_map = { statut_map = {
"SIGNE": StatutSignatureEnum.SIGNE, "SIGNE": StatutSignatureDB.SIGNE,
"REFUSE": StatutSignatureEnum.REFUSE, "REFUSE": StatutSignatureDB.REFUSE,
"EXPIRE": StatutSignatureEnum.EXPIRE, "EXPIRE": StatutSignatureDB.EXPIRE,
} }
nouveau = statut_map.get(statut_universign["statut"]) nouveau = statut_map.get(statut_universign["statut"])
@ -1524,7 +1524,7 @@ async def rafraichir_statuts_signatures(session: AsyncSession = Depends(get_sess
@app.post("/devis/{id}/signer", tags=["Devis"]) @app.post("/devis/{id}/signer", tags=["Devis"])
async def envoyer_devis_signature( async def envoyer_devis_signature(
id: str, request: SignatureRequest, session: AsyncSession = Depends(get_session) id: str, request: Signature, session: AsyncSession = Depends(get_session)
): ):
try: try:
devis = sage_client.lire_devis(id) devis = sage_client.lire_devis(id)
@ -1548,7 +1548,7 @@ async def envoyer_devis_signature(
signer_url=resultat["signer_url"], signer_url=resultat["signer_url"],
email_signataire=request.email_signataire, email_signataire=request.email_signataire,
nom_signataire=request.nom_signataire, nom_signataire=request.nom_signataire,
statut=StatutSignatureEnum.ENVOYE, statut=StatutSignatureDB.ENVOYE,
date_envoi=datetime.now(), date_envoi=datetime.now(),
) )
@ -1594,7 +1594,7 @@ async def envoyer_emails_lot(
corps_html=batch.corps_html, corps_html=batch.corps_html,
document_ids=",".join(batch.document_ids) if batch.document_ids else None, document_ids=",".join(batch.document_ids) if batch.document_ids else None,
type_document=batch.type_document, type_document=batch.type_document,
statut=StatutEmailEnum.EN_ATTENTE, statut=StatutEmailDB.EN_ATTENTE,
date_creation=datetime.now(), date_creation=datetime.now(),
nb_tentatives=0, nb_tentatives=0,
) )
@ -1663,7 +1663,7 @@ async def valider_remise(
@app.post("/devis/{id}/relancer-signature", tags=["Devis"]) @app.post("/devis/{id}/relancer-signature", tags=["Devis"])
async def relancer_devis_signature( async def relancer_devis_signature(
id: str, relance: RelanceDevisRequest, session: AsyncSession = Depends(get_session) id: str, relance: RelanceDevis, session: AsyncSession = Depends(get_session)
): ):
try: try:
devis = sage_client.lire_devis(id) devis = sage_client.lire_devis(id)
@ -1694,7 +1694,7 @@ async def relancer_devis_signature(
signer_url=resultat["signer_url"], signer_url=resultat["signer_url"],
email_signataire=contact["email"], email_signataire=contact["email"],
nom_signataire=contact["nom"] or contact["client_intitule"], nom_signataire=contact["nom"] or contact["client_intitule"],
statut=StatutSignatureEnum.ENVOYE, statut=StatutSignatureDB.ENVOYE,
date_envoi=datetime.now(), date_envoi=datetime.now(),
est_relance=True, est_relance=True,
nb_relances=1, nb_relances=1,
@ -1786,7 +1786,7 @@ class RelanceFactureRequest(BaseModel):
@app.post("/factures", status_code=201, tags=["Factures"]) @app.post("/factures", status_code=201, tags=["Factures"])
async def creer_facture( async def creer_facture(
facture: FactureCreateRequest, session: AsyncSession = Depends(get_session) facture: FactureCreate, session: AsyncSession = Depends(get_session)
): ):
try: try:
facture_data = { facture_data = {
@ -1836,7 +1836,7 @@ async def creer_facture(
@app.put("/factures/{id}", tags=["Factures"]) @app.put("/factures/{id}", tags=["Factures"])
async def modifier_facture( async def modifier_facture(
id: str, id: str,
facture_update: FactureUpdateRequest, facture_update: FactureUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -1940,7 +1940,7 @@ async def relancer_facture(
corps_html=corps, corps_html=corps,
document_ids=id, document_ids=id,
type_document=TypeDocument.FACTURE, type_document=TypeDocument.FACTURE,
statut=StatutEmailEnum.EN_ATTENTE, statut=StatutEmailDB.EN_ATTENTE,
date_creation=datetime.now(), date_creation=datetime.now(),
nb_tentatives=0, nb_tentatives=0,
) )
@ -1979,7 +1979,7 @@ async def journal_emails(
query = select(EmailLog) query = select(EmailLog)
if statut: if statut:
query = query.where(EmailLog.statut == StatutEmailEnum[statut.value]) query = query.where(EmailLog.statut == StatutEmailDB[statut.value])
if destinataire: if destinataire:
query = query.where(EmailLog.destinataire.contains(destinataire)) query = query.where(EmailLog.destinataire.contains(destinataire))
@ -2012,7 +2012,7 @@ async def exporter_logs_csv(
): ):
query = select(EmailLog) query = select(EmailLog)
if statut: if statut:
query = query.where(EmailLog.statut == StatutEmailEnum[statut.value]) query = query.where(EmailLog.statut == StatutEmailDB[statut.value])
query = query.order_by(EmailLog.date_creation.desc()) query = query.order_by(EmailLog.date_creation.desc())
@ -2226,7 +2226,7 @@ async def rechercher_fournisseurs(query: Optional[str] = Query(None)):
@app.post("/fournisseurs", status_code=201, tags=["Fournisseurs"]) @app.post("/fournisseurs", status_code=201, tags=["Fournisseurs"])
async def ajouter_fournisseur( async def ajouter_fournisseur(
fournisseur: FournisseurCreateAPIRequest, fournisseur: FournisseurCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -2254,7 +2254,7 @@ async def ajouter_fournisseur(
) )
async def modifier_fournisseur( async def modifier_fournisseur(
code: str, code: str,
fournisseur_update: FournisseurUpdateRequest, fournisseur_update: FournisseurUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -2315,9 +2315,7 @@ async def lire_avoir(numero: str):
@app.post("/avoirs", status_code=201, tags=["Avoirs"]) @app.post("/avoirs", status_code=201, tags=["Avoirs"])
async def creer_avoir( async def creer_avoir(avoir: AvoirCreate, session: AsyncSession = Depends(get_session)):
avoir: AvoirCreateRequest, session: AsyncSession = Depends(get_session)
):
try: try:
avoir_data = { avoir_data = {
"client_id": avoir.client_id, "client_id": avoir.client_id,
@ -2364,7 +2362,7 @@ async def creer_avoir(
@app.put("/avoirs/{id}", tags=["Avoirs"]) @app.put("/avoirs/{id}", tags=["Avoirs"])
async def modifier_avoir( async def modifier_avoir(
id: str, id: str,
avoir_update: AvoirUpdateRequest, avoir_update: AvoirUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -2434,7 +2432,7 @@ async def lire_livraison(numero: str):
@app.post("/livraisons", status_code=201, tags=["Livraisons"]) @app.post("/livraisons", status_code=201, tags=["Livraisons"])
async def creer_livraison( async def creer_livraison(
livraison: LivraisonCreateRequest, session: AsyncSession = Depends(get_session) livraison: LivraisonCreate, session: AsyncSession = Depends(get_session)
): ):
try: try:
livraison_data = { livraison_data = {
@ -2488,7 +2486,7 @@ async def creer_livraison(
@app.put("/livraisons/{id}", tags=["Livraisons"]) @app.put("/livraisons/{id}", tags=["Livraisons"])
async def modifier_livraison( async def modifier_livraison(
id: str, id: str,
livraison_update: LivraisonUpdateRequest, livraison_update: LivraisonUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
try: try:
@ -2694,7 +2692,7 @@ async def commande_vers_livraison(
@app.get( @app.get(
"/familles", "/familles",
response_model=List[FamilleResponse], response_model=List[Familles],
tags=["Familles"], tags=["Familles"],
summary="Liste toutes les familles d'articles", summary="Liste toutes les familles d'articles",
) )
@ -2706,7 +2704,7 @@ async def lister_familles(
logger.info(f"{len(familles)} famille(s) retournée(s)") logger.info(f"{len(familles)} famille(s) retournée(s)")
return [FamilleResponse(**f) for f in familles] return [Familles(**f) for f in familles]
except Exception as e: except Exception as e:
logger.error(f"Erreur liste familles: {e}", exc_info=True) logger.error(f"Erreur liste familles: {e}", exc_info=True)
@ -2718,7 +2716,7 @@ async def lister_familles(
@app.get( @app.get(
"/familles/{code}", "/familles/{code}",
response_model=FamilleResponse, response_model=Familles,
tags=["Familles"], tags=["Familles"],
summary="Lecture d'une famille par son code", summary="Lecture d'une famille par son code",
) )
@ -2737,7 +2735,7 @@ async def lire_famille(
logger.info(f"Famille {code} lue: {famille.get('intitule', '')}") logger.info(f"Famille {code} lue: {famille.get('intitule', '')}")
return FamilleResponse(**famille) return Familles(**famille)
except HTTPException: except HTTPException:
raise raise
@ -2751,12 +2749,12 @@ async def lire_famille(
@app.post( @app.post(
"/familles", "/familles",
response_model=FamilleResponse, response_model=Familles,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
tags=["Familles"], tags=["Familles"],
summary="Création d'une famille d'articles", summary="Création d'une famille d'articles",
) )
async def creer_famille(famille: FamilleCreateRequest): async def creer_famille(famille: FamilleCreate):
try: try:
if not famille.code or not famille.intitule: if not famille.code or not famille.intitule:
raise HTTPException( raise HTTPException(
@ -2772,7 +2770,7 @@ async def creer_famille(famille: FamilleCreateRequest):
logger.info(f"Famille créée: {resultat.get('code')}") logger.info(f"Famille créée: {resultat.get('code')}")
return FamilleResponse(**resultat) return Familles(**resultat)
except ValueError as e: except ValueError as e:
logger.warning(f"Erreur métier création famille: {e}") logger.warning(f"Erreur métier création famille: {e}")
@ -2791,12 +2789,12 @@ async def creer_famille(famille: FamilleCreateRequest):
@app.post( @app.post(
"/stock/entree", "/stock/entree",
response_model=MouvementStockResponse, response_model=MouvementStock,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
tags=["Stock"], tags=["Stock"],
summary="ENTRÉE EN STOCK : Ajoute des articles dans le stock", summary="ENTRÉE EN STOCK : Ajoute des articles dans le stock",
) )
async def creer_entree_stock(entree: EntreeStockRequest): async def creer_entree_stock(entree: EntreeStock):
try: try:
entree_data = entree.dict() entree_data = entree.dict()
if entree_data.get("date_entree"): if entree_data.get("date_entree"):
@ -2808,7 +2806,7 @@ async def creer_entree_stock(entree: EntreeStockRequest):
logger.info(f"Entrée stock créée: {resultat.get('numero')}") logger.info(f"Entrée stock créée: {resultat.get('numero')}")
return MouvementStockResponse(**resultat) return MouvementStock(**resultat)
except ValueError as e: except ValueError as e:
logger.warning(f"Erreur métier entrée stock: {e}") logger.warning(f"Erreur métier entrée stock: {e}")
@ -2824,12 +2822,12 @@ async def creer_entree_stock(entree: EntreeStockRequest):
@app.post( @app.post(
"/stock/sortie", "/stock/sortie",
response_model=MouvementStockResponse, response_model=MouvementStock,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
tags=["Stock"], tags=["Stock"],
summary="SORTIE DE STOCK : Retire des articles du stock", summary="SORTIE DE STOCK : Retire des articles du stock",
) )
async def creer_sortie_stock(sortie: SortieStockRequest): async def creer_sortie_stock(sortie: SortieStock):
try: try:
sortie_data = sortie.dict() sortie_data = sortie.dict()
if sortie_data.get("date_sortie"): if sortie_data.get("date_sortie"):
@ -2841,7 +2839,7 @@ async def creer_sortie_stock(sortie: SortieStockRequest):
logger.info(f"Sortie stock créée: {resultat.get('numero')}") logger.info(f"Sortie stock créée: {resultat.get('numero')}")
return MouvementStockResponse(**resultat) return MouvementStock(**resultat)
except ValueError as e: except ValueError as e:
logger.warning(f"Erreur métier sortie stock: {e}") logger.warning(f"Erreur métier sortie stock: {e}")
@ -2857,7 +2855,7 @@ async def creer_sortie_stock(sortie: SortieStockRequest):
@app.get( @app.get(
"/stock/mouvement/{numero}", "/stock/mouvement/{numero}",
response_model=MouvementStockResponse, response_model=MouvementStock,
tags=["Stock"], tags=["Stock"],
summary="Lecture d'un mouvement de stock", summary="Lecture d'un mouvement de stock",
) )
@ -2876,7 +2874,7 @@ async def lire_mouvement_stock(
logger.info(f"Mouvement {numero} lu") logger.info(f"Mouvement {numero} lu")
return MouvementStockResponse(**mouvement) return MouvementStock(**mouvement)
except HTTPException: except HTTPException:
raise raise
@ -2907,7 +2905,7 @@ async def statistiques_familles():
) )
@app.get("/debug/users", response_model=List[UserResponse], tags=["Debug"]) @app.get("/debug/users", response_model=List[Users], tags=["Debug"])
async def lister_utilisateurs_debug( async def lister_utilisateurs_debug(
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
limit: int = Query(100, le=1000), limit: int = Query(100, le=1000),
@ -2934,7 +2932,7 @@ async def lister_utilisateurs_debug(
users_response = [] users_response = []
for user in users: for user in users:
users_response.append( users_response.append(
UserResponse( Users(
id=user.id, id=user.id,
email=user.email, email=user.email,
nom=user.nom, nom=user.nom,

View file

@ -12,7 +12,7 @@ from schemas import (
SageGatewayCreate, SageGatewayCreate,
SageGatewayUpdate, SageGatewayUpdate,
SageGatewayResponse, SageGatewayResponse,
SageGatewayListResponse, SageGatewayList,
SageGatewayHealthCheck, SageGatewayHealthCheck,
SageGatewayTestRequest, SageGatewayTestRequest,
SageGatewayStatsResponse, SageGatewayStatsResponse,
@ -41,7 +41,7 @@ async def create_gateway(
return SageGatewayResponse(**gateway_response_from_model(gateway)) return SageGatewayResponse(**gateway_response_from_model(gateway))
@router.get("", response_model=SageGatewayListResponse) @router.get("", response_model=SageGatewayList)
async def list_gateways( async def list_gateways(
include_deleted: bool = Query(False, description="Inclure les gateways supprimées"), include_deleted: bool = Query(False, description="Inclure les gateways supprimées"),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
@ -54,7 +54,7 @@ async def list_gateways(
items = [SageGatewayResponse(**gateway_response_from_model(g)) for g in gateways] items = [SageGatewayResponse(**gateway_response_from_model(g)) for g in gateways]
return SageGatewayListResponse( return SageGatewayList(
items=items, items=items,
total=len(items), total=len(items),
active_gateway=SageGatewayResponse(**gateway_response_from_model(active)) active_gateway=SageGatewayResponse(**gateway_response_from_model(active))

View file

@ -1,52 +1,52 @@
from schemas.tiers.tiers import TiersDetails, TypeTiersInt from schemas.tiers.tiers import TiersDetails, TypeTiersInt
from schemas.tiers.type_tiers import TypeTiers from schemas.tiers.type_tiers import TypeTiers
from schemas.schema_mixte import BaremeRemiseResponse from schemas.schema_mixte import BaremeRemiseResponse
from schemas.user import UserResponse from schemas.user import Users
from schemas.tiers.clients import ( from schemas.tiers.clients import (
ClientCreateRequest, ClientCreate,
ClientDetails, ClientDetails,
ClientResponse, ClientResponse,
ClientUpdateRequest, ClientUpdate,
) )
from schemas.tiers.contact import Contact, ContactCreate, ContactUpdate from schemas.tiers.contact import Contact, ContactCreate, ContactUpdate
from schemas.tiers.fournisseurs import ( from schemas.tiers.fournisseurs import (
FournisseurCreateAPIRequest, FournisseurCreate,
FournisseurDetails, FournisseurDetails,
FournisseurUpdateRequest, FournisseurUpdate,
) )
from schemas.documents.avoirs import AvoirCreateRequest, AvoirUpdateRequest from schemas.documents.avoirs import AvoirCreate, AvoirUpdate
from schemas.documents.commandes import CommandeCreateRequest, CommandeUpdateRequest from schemas.documents.commandes import CommandeCreate, CommandeUpdate
from schemas.documents.devis import ( from schemas.documents.devis import (
DevisRequest, DevisRequest,
DevisResponse, Devis,
DevisUpdateRequest, DevisUpdate,
RelanceDevisRequest, RelanceDevis,
) )
from schemas.documents.documents import TypeDocument, TypeDocumentSQL from schemas.documents.documents import TypeDocument, TypeDocumentSQL
from schemas.documents.email import StatutEmail, EmailEnvoiRequest from schemas.documents.email import StatutEmail, EmailEnvoi
from schemas.documents.factures import FactureCreateRequest, FactureUpdateRequest from schemas.documents.factures import FactureCreate, FactureUpdate
from schemas.documents.livraisons import LivraisonCreateRequest, LivraisonUpdateRequest from schemas.documents.livraisons import LivraisonCreate, LivraisonUpdate
from schemas.documents.universign import SignatureRequest, StatutSignature from schemas.documents.universign import Signature, StatutSignature
from schemas.articles.articles import ( from schemas.articles.articles import (
ArticleCreateRequest, ArticleCreate,
ArticleResponse, Article,
ArticleUpdateRequest, ArticleUpdate,
ArticleListResponse, ArticleList,
EntreeStockRequest, EntreeStock,
SortieStockRequest, SortieStock,
MouvementStockResponse, MouvementStock,
) )
from schemas.articles.famille_article import ( from schemas.articles.famille_article import (
FamilleResponse, Familles,
FamilleCreateRequest, FamilleCreate,
FamilleListResponse, FamilleList,
) )
from schemas.sage.sage_gateway import ( from schemas.sage.sage_gateway import (
SageGatewayCreate, SageGatewayCreate,
SageGatewayUpdate, SageGatewayUpdate,
SageGatewayResponse, SageGatewayResponse,
SageGatewayListResponse, SageGatewayList,
SageGatewayHealthCheck, SageGatewayHealthCheck,
SageGatewayTestRequest, SageGatewayTestRequest,
SageGatewayStatsResponse, SageGatewayStatsResponse,
@ -57,50 +57,50 @@ __all__ = [
"TiersDetails", "TiersDetails",
"TypeTiers", "TypeTiers",
"BaremeRemiseResponse", "BaremeRemiseResponse",
"UserResponse", "Users",
"ClientCreateRequest", "ClientCreate",
"ClientDetails", "ClientDetails",
"ClientResponse", "ClientResponse",
"ClientUpdateRequest", "ClientUpdate",
"FournisseurCreateAPIRequest", "FournisseurCreate",
"FournisseurDetails", "FournisseurDetails",
"FournisseurUpdateRequest", "FournisseurUpdate",
"Contact", "Contact",
"AvoirCreateRequest", "AvoirCreate",
"AvoirUpdateRequest", "AvoirUpdate",
"CommandeCreateRequest", "CommandeCreate",
"CommandeUpdateRequest", "CommandeUpdate",
"DevisRequest", "DevisRequest",
"DevisResponse", "Devis",
"DevisUpdateRequest", "DevisUpdate",
"TypeDocument", "TypeDocument",
"TypeDocumentSQL", "TypeDocumentSQL",
"StatutEmail", "StatutEmail",
"EmailEnvoiRequest", "EmailEnvoi",
"FactureCreateRequest", "FactureCreate",
"FactureUpdateRequest", "FactureUpdate",
"LivraisonCreateRequest", "LivraisonCreate",
"LivraisonUpdateRequest", "LivraisonUpdate",
"SignatureRequest", "Signature",
"StatutSignature", "StatutSignature",
"TypeTiersInt", "TypeTiersInt",
"ArticleCreateRequest", "ArticleCreate",
"ArticleResponse", "Article",
"ArticleUpdateRequest", "ArticleUpdate",
"ArticleListResponse", "ArticleList",
"EntreeStockRequest", "EntreeStock",
"SortieStockRequest", "SortieStock",
"MouvementStockResponse", "MouvementStock",
"RelanceDevisRequest", "RelanceDevis",
"FamilleResponse", "Familles",
"FamilleCreateRequest", "FamilleCreate",
"FamilleListResponse", "FamilleList",
"ContactCreate", "ContactCreate",
"ContactUpdate", "ContactUpdate",
"SageGatewayCreate", "SageGatewayCreate",
"SageGatewayUpdate", "SageGatewayUpdate",
"SageGatewayResponse", "SageGatewayResponse",
"SageGatewayListResponse", "SageGatewayList",
"SageGatewayHealthCheck", "SageGatewayHealthCheck",
"SageGatewayTestRequest", "SageGatewayTestRequest",
"SageGatewayStatsResponse", "SageGatewayStatsResponse",

View file

@ -1,6 +1,6 @@
from pydantic import BaseModel, Field, validator, field_validator from pydantic import BaseModel, Field, validator, field_validator
from typing import List, Optional from typing import List, Optional
from datetime import date, datetime from datetime import date
from utils import ( from utils import (
NomenclatureType, NomenclatureType,
@ -10,364 +10,7 @@ from utils import (
normalize_string_field, normalize_string_field,
) )
class Article(BaseModel):
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""" """Article complet avec tous les enrichissements disponibles"""
reference: str = Field(..., description="Référence article (AR_Ref)") reference: str = Field(..., description="Référence article (AR_Ref)")
@ -763,11 +406,11 @@ class ArticleResponse(BaseModel):
} }
class ArticleListResponse(BaseModel): class ArticleList(BaseModel):
"""Réponse pour une liste d'articles""" """Réponse pour une liste d'articles"""
total: int = Field(..., description="Nombre total d'articles") total: int = Field(..., description="Nombre total d'articles")
articles: List[ArticleResponse] = Field(..., description="Liste des articles") articles: List[Article] = Field(..., description="Liste des articles")
filtre_applique: Optional[str] = Field( filtre_applique: Optional[str] = Field(
None, description="Filtre de recherche appliqué" None, description="Filtre de recherche appliqué"
) )
@ -780,7 +423,7 @@ class ArticleListResponse(BaseModel):
) )
class ArticleCreateRequest(BaseModel): class ArticleCreate(BaseModel):
"""Schéma pour création d'article""" """Schéma pour création d'article"""
reference: str = Field(..., max_length=18, description="Référence article") reference: str = Field(..., max_length=18, description="Référence article")
@ -796,7 +439,7 @@ class ArticleCreateRequest(BaseModel):
description: Optional[str] = Field(None, description="Description") description: Optional[str] = Field(None, description="Description")
class ArticleUpdateRequest(BaseModel): class ArticleUpdate(BaseModel):
"""Schéma pour modification d'article""" """Schéma pour modification d'article"""
designation: Optional[str] = Field(None, max_length=69) designation: Optional[str] = Field(None, max_length=69)
@ -810,7 +453,7 @@ class ArticleUpdateRequest(BaseModel):
description: Optional[str] = Field(None) description: Optional[str] = Field(None)
class MouvementStockLigneRequest(BaseModel): class MouvementStockLigne(BaseModel):
article_ref: str = Field(..., description="Référence de l'article") article_ref: str = Field(..., description="Référence de l'article")
quantite: float = Field(..., gt=0, description="Quantité (>0)") quantite: float = Field(..., gt=0, description="Quantité (>0)")
depot_code: Optional[str] = Field(None, description="Code du dépôt (ex: '01')") depot_code: Optional[str] = Field(None, description="Code du dépôt (ex: '01')")
@ -864,7 +507,7 @@ class MouvementStockLigneRequest(BaseModel):
return v return v
class EntreeStockRequest(BaseModel): class EntreeStock(BaseModel):
"""Création d'un bon d'entrée en stock""" """Création d'un bon d'entrée en stock"""
date_entree: Optional[date] = Field( date_entree: Optional[date] = Field(
@ -874,7 +517,7 @@ class EntreeStockRequest(BaseModel):
depot_code: Optional[str] = Field( depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)" None, description="Dépôt principal (si applicable)"
) )
lignes: List[MouvementStockLigneRequest] = Field( lignes: List[MouvementStockLigne] = Field(
..., min_items=1, description="Lignes du mouvement" ..., min_items=1, description="Lignes du mouvement"
) )
commentaire: Optional[str] = Field(None, description="Commentaire général") commentaire: Optional[str] = Field(None, description="Commentaire général")
@ -899,7 +542,7 @@ class EntreeStockRequest(BaseModel):
} }
class SortieStockRequest(BaseModel): class SortieStock(BaseModel):
"""Création d'un bon de sortie de stock""" """Création d'un bon de sortie de stock"""
date_sortie: Optional[date] = Field( date_sortie: Optional[date] = Field(
@ -909,7 +552,7 @@ class SortieStockRequest(BaseModel):
depot_code: Optional[str] = Field( depot_code: Optional[str] = Field(
None, description="Dépôt principal (si applicable)" None, description="Dépôt principal (si applicable)"
) )
lignes: List[MouvementStockLigneRequest] = Field( lignes: List[MouvementStockLigne] = Field(
..., min_items=1, description="Lignes du mouvement" ..., min_items=1, description="Lignes du mouvement"
) )
commentaire: Optional[str] = Field(None, description="Commentaire général") commentaire: Optional[str] = Field(None, description="Commentaire général")
@ -933,7 +576,7 @@ class SortieStockRequest(BaseModel):
} }
class MouvementStockResponse(BaseModel): class MouvementStock(BaseModel):
"""Réponse pour un mouvement de stock""" """Réponse pour un mouvement de stock"""
article_ref: str = Field(..., description="Numéro d'article") article_ref: str = Field(..., description="Numéro d'article")

View file

@ -2,7 +2,7 @@ from pydantic import BaseModel, Field
from typing import Optional from typing import Optional
class FamilleCreateRequest(BaseModel): class FamilleCreate(BaseModel):
"""Schéma pour création de famille d'articles""" """Schéma pour création de famille d'articles"""
code: str = Field(..., max_length=18, description="Code famille (max 18 car)") code: str = Field(..., max_length=18, description="Code famille (max 18 car)")
@ -27,7 +27,7 @@ class FamilleCreateRequest(BaseModel):
} }
class FamilleResponse(BaseModel): class Familles(BaseModel):
"""Modèle complet d'une famille avec données comptables et fournisseur""" """Modèle complet d'une famille avec données comptables et fournisseur"""
code: str = Field(..., description="Code famille") code: str = Field(..., description="Code famille")
@ -236,10 +236,10 @@ class FamilleResponse(BaseModel):
} }
class FamilleListResponse(BaseModel): class FamilleList(BaseModel):
"""Réponse pour la liste des familles""" """Réponse pour la liste des familles"""
familles: list[FamilleResponse] familles: list[Familles]
total: int total: int
filtre: Optional[str] = None filtre: Optional[str] = None
inclure_totaux: bool = True inclure_totaux: bool = True

View file

@ -13,7 +13,7 @@ class LigneAvoir(BaseModel):
return v.replace("\xa0", "").strip() return v.replace("\xa0", "").strip()
class AvoirCreateRequest(BaseModel): class AvoirCreate(BaseModel):
client_id: str client_id: str
date_avoir: Optional[date] = None date_avoir: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
@ -38,7 +38,7 @@ class AvoirCreateRequest(BaseModel):
} }
class AvoirUpdateRequest(BaseModel): class AvoirUpdate(BaseModel):
date_avoir: Optional[date] = None date_avoir: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
lignes: Optional[List[LigneAvoir]] = None lignes: Optional[List[LigneAvoir]] = None

View file

@ -13,7 +13,7 @@ class LigneCommande(BaseModel):
return v.replace("\xa0", "").strip() return v.replace("\xa0", "").strip()
class CommandeCreateRequest(BaseModel): class CommandeCreate(BaseModel):
client_id: str client_id: str
date_commande: Optional[date] = None date_commande: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
@ -38,7 +38,7 @@ class CommandeCreateRequest(BaseModel):
} }
class CommandeUpdateRequest(BaseModel): class CommandeUpdate(BaseModel):
date_commande: Optional[date] = None date_commande: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
lignes: Optional[List[LigneCommande]] = None lignes: Optional[List[LigneCommande]] = None

View file

@ -21,7 +21,7 @@ class DevisRequest(BaseModel):
lignes: List[LigneDevis] lignes: List[LigneDevis]
class DevisResponse(BaseModel): class Devis(BaseModel):
id: str id: str
client_id: str client_id: str
date_devis: str date_devis: str
@ -30,7 +30,7 @@ class DevisResponse(BaseModel):
nb_lignes: int nb_lignes: int
class DevisUpdateRequest(BaseModel): class DevisUpdate(BaseModel):
"""Modèle pour modification d'un devis existant""" """Modèle pour modification d'un devis existant"""
date_devis: Optional[date] = None date_devis: Optional[date] = None
@ -58,6 +58,6 @@ class DevisUpdateRequest(BaseModel):
} }
class RelanceDevisRequest(BaseModel): class RelanceDevis(BaseModel):
doc_id: str doc_id: str
message_personnalise: Optional[str] = None message_personnalise: Optional[str] = None

View file

@ -13,7 +13,7 @@ class StatutEmail(str, Enum):
BOUNCE = "BOUNCE" BOUNCE = "BOUNCE"
class EmailEnvoiRequest(BaseModel): class EmailEnvoi(BaseModel):
destinataire: EmailStr destinataire: EmailStr
cc: Optional[List[EmailStr]] = [] cc: Optional[List[EmailStr]] = []
cci: Optional[List[EmailStr]] = [] cci: Optional[List[EmailStr]] = []

View file

@ -13,7 +13,7 @@ class LigneFacture(BaseModel):
return v.replace("\xa0", "").strip() return v.replace("\xa0", "").strip()
class FactureCreateRequest(BaseModel): class FactureCreate(BaseModel):
client_id: str client_id: str
date_facture: Optional[date] = None date_facture: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
@ -38,7 +38,7 @@ class FactureCreateRequest(BaseModel):
} }
class FactureUpdateRequest(BaseModel): class FactureUpdate(BaseModel):
date_facture: Optional[date] = None date_facture: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
lignes: Optional[List[LigneFacture]] = None lignes: Optional[List[LigneFacture]] = None

View file

@ -13,7 +13,7 @@ class LigneLivraison(BaseModel):
return v.replace("\xa0", "").strip() return v.replace("\xa0", "").strip()
class LivraisonCreateRequest(BaseModel): class LivraisonCreate(BaseModel):
client_id: str client_id: str
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
date_livraison_prevue: Optional[date] = None date_livraison_prevue: Optional[date] = None
@ -38,7 +38,7 @@ class LivraisonCreateRequest(BaseModel):
} }
class LivraisonUpdateRequest(BaseModel): class LivraisonUpdate(BaseModel):
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
date_livraison_prevue: Optional[date] = None date_livraison_prevue: Optional[date] = None
lignes: Optional[List[LigneLivraison]] = None lignes: Optional[List[LigneLivraison]] = None

View file

@ -11,7 +11,7 @@ class StatutSignature(str, Enum):
EXPIRE = "EXPIRE" EXPIRE = "EXPIRE"
class SignatureRequest(BaseModel): class Signature(BaseModel):
doc_id: str doc_id: str
type_doc: TypeDocument type_doc: TypeDocument
email_signataire: EmailStr email_signataire: EmailStr

View file

@ -110,7 +110,7 @@ class SageGatewayResponse(BaseModel):
from_attributes = True from_attributes = True
class SageGatewayListResponse(BaseModel): class SageGatewayList(BaseModel):
items: List[SageGatewayResponse] items: List[SageGatewayResponse]
total: int total: int

View file

@ -277,7 +277,7 @@ class ClientDetails(BaseModel):
} }
class ClientCreateRequest(BaseModel): class ClientCreate(BaseModel):
intitule: str = Field( intitule: str = Field(
..., max_length=69, description="Nom du client (CT_Intitule) - OBLIGATOIRE" ..., max_length=69, description="Nom du client (CT_Intitule) - OBLIGATOIRE"
) )
@ -679,7 +679,7 @@ class ClientCreateRequest(BaseModel):
} }
class ClientUpdateRequest(BaseModel): class ClientUpdate(BaseModel):
intitule: Optional[str] = Field(None, max_length=69) intitule: Optional[str] = Field(None, max_length=69)
qualite: Optional[str] = Field(None, max_length=17) qualite: Optional[str] = Field(None, max_length=17)
classement: Optional[str] = Field(None, max_length=17) classement: Optional[str] = Field(None, max_length=17)

View file

@ -267,7 +267,7 @@ class FournisseurDetails(BaseModel):
} }
class FournisseurCreateAPIRequest(BaseModel): class FournisseurCreate(BaseModel):
intitule: str = Field( intitule: str = Field(
..., min_length=1, max_length=69, description="Raison sociale du fournisseur" ..., min_length=1, max_length=69, description="Raison sociale du fournisseur"
) )
@ -304,7 +304,7 @@ class FournisseurCreateAPIRequest(BaseModel):
} }
class FournisseurUpdateRequest(BaseModel): class FournisseurUpdate(BaseModel):
intitule: Optional[str] = Field(None, min_length=1, max_length=69) intitule: Optional[str] = Field(None, min_length=1, max_length=69)
adresse: Optional[str] = Field(None, max_length=35) adresse: Optional[str] = Field(None, max_length=35)
code_postal: Optional[str] = Field(None, max_length=9) code_postal: Optional[str] = Field(None, max_length=9)

View file

@ -2,7 +2,7 @@ from pydantic import BaseModel
from typing import Optional from typing import Optional
class UserResponse(BaseModel): class Users(BaseModel):
id: str id: str
email: str email: str
nom: str nom: str