Unification for document's schemas

This commit is contained in:
Fanilo-Nantenaina 2026-01-05 17:22:27 +03:00
parent e7bdf2d6a2
commit 8850c7c266
9 changed files with 107 additions and 121 deletions

100
api.py
View file

@ -78,7 +78,7 @@ from core.sage_context import (
get_gateway_context_for_user, get_gateway_context_for_user,
GatewayContext, GatewayContext,
) )
from utils.generic_functions import _preparer_lignes_document
if os.path.exists("/app"): if os.path.exists("/app"):
LOGS_DIR = FilePath("/app/logs") LOGS_DIR = FilePath("/app/logs")
@ -526,19 +526,15 @@ async def creer_devis(devis: DevisRequest):
devis.date_livraison.isoformat() if devis.date_livraison else None devis.date_livraison.isoformat() if devis.date_livraison else None
), ),
"reference": devis.reference, "reference": devis.reference,
"lignes": [ "lignes": _preparer_lignes_document(devis.lignes),
{
"article_code": ligne.article_code,
"quantite": ligne.quantite,
"remise_pourcentage": ligne.remise_pourcentage,
}
for ligne in devis.lignes
],
} }
resultat = sage_client.creer_devis(devis_data) resultat = sage_client.creer_devis(devis_data)
logger.info(f"Devis créé: {resultat.get('numero_devis')}") logger.info(
f"✅ Devis créé: {resultat.get('numero_devis')} "
f"({resultat.get('total_ttc')}€ TTC)"
)
return Devis( return Devis(
id=resultat["numero_devis"], id=resultat["numero_devis"],
@ -550,7 +546,7 @@ async def creer_devis(devis: DevisRequest):
) )
except Exception as e: except Exception as e:
logger.error(f"Erreur création devis: {e}") logger.error(f"Erreur création devis: {e}")
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@ -613,19 +609,15 @@ async def creer_commande(
commande.date_livraison.isoformat() if commande.date_livraison else None commande.date_livraison.isoformat() if commande.date_livraison else None
), ),
"reference": commande.reference, "reference": commande.reference,
"lignes": [ "lignes": _preparer_lignes_document(commande.lignes),
{
"article_code": ligne.article_code,
"quantite": ligne.quantite,
"remise_pourcentage": ligne.remise_pourcentage,
}
for ligne in commande.lignes
],
} }
resultat = sage_client.creer_commande(commande_data) resultat = sage_client.creer_commande(commande_data)
logger.info(f"Commande créée: {resultat.get('numero_commande')}") logger.info(
f"✅ Commande créée: {resultat.get('numero_commande')} "
f"({resultat.get('total_ttc')}€ TTC)"
)
return { return {
"success": True, "success": True,
@ -637,14 +629,15 @@ async def creer_commande(
"total_ht": resultat["total_ht"], "total_ht": resultat["total_ht"],
"total_ttc": resultat["total_ttc"], "total_ttc": resultat["total_ttc"],
"nb_lignes": resultat["nb_lignes"], "nb_lignes": resultat["nb_lignes"],
"reference": commande.reference, "reference": resultat.get("reference"),
"date_livraison": resultat.get("date_livraison"),
}, },
} }
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error(f"Erreur création commande: {e}") logger.error(f"Erreur création commande: {e}")
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@ -1798,19 +1791,15 @@ async def creer_facture(
facture.date_livraison.isoformat() if facture.date_livraison else None facture.date_livraison.isoformat() if facture.date_livraison else None
), ),
"reference": facture.reference, "reference": facture.reference,
"lignes": [ "lignes": _preparer_lignes_document(facture.lignes),
{
"article_code": ligne.article_code,
"quantite": ligne.quantite,
"remise_pourcentage": ligne.remise_pourcentage,
}
for ligne in facture.lignes
],
} }
resultat = sage_client.creer_facture(facture_data) resultat = sage_client.creer_facture(facture_data)
logger.info(f"Facture créée: {resultat.get('numero_facture')}") logger.info(
f"✅ Facture créée: {resultat.get('numero_facture')} "
f"({resultat.get('total_ttc')}€ TTC)"
)
return { return {
"success": True, "success": True,
@ -1822,14 +1811,15 @@ async def creer_facture(
"total_ht": resultat["total_ht"], "total_ht": resultat["total_ht"],
"total_ttc": resultat["total_ttc"], "total_ttc": resultat["total_ttc"],
"nb_lignes": resultat["nb_lignes"], "nb_lignes": resultat["nb_lignes"],
"reference": facture.reference, "reference": resultat.get("reference"),
"date_livraison": resultat.get("date_livraison"),
}, },
} }
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error(f"Erreur création facture: {e}") logger.error(f"Erreur création facture: {e}")
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@ -2324,19 +2314,15 @@ async def creer_avoir(avoir: AvoirCreate, session: AsyncSession = Depends(get_se
avoir.date_livraison.isoformat() if avoir.date_livraison else None avoir.date_livraison.isoformat() if avoir.date_livraison else None
), ),
"reference": avoir.reference, "reference": avoir.reference,
"lignes": [ "lignes": _preparer_lignes_document(avoir.lignes),
{
"article_code": ligne.article_code,
"quantite": ligne.quantite,
"remise_pourcentage": ligne.remise_pourcentage,
}
for ligne in avoir.lignes
],
} }
resultat = sage_client.creer_avoir(avoir_data) resultat = sage_client.creer_avoir(avoir_data)
logger.info(f"Avoir créé: {resultat.get('numero_avoir')}") logger.info(
f"✅ Avoir créé: {resultat.get('numero_avoir')} "
f"({resultat.get('total_ttc')}€ TTC)"
)
return { return {
"success": True, "success": True,
@ -2348,14 +2334,15 @@ async def creer_avoir(avoir: AvoirCreate, session: AsyncSession = Depends(get_se
"total_ht": resultat["total_ht"], "total_ht": resultat["total_ht"],
"total_ttc": resultat["total_ttc"], "total_ttc": resultat["total_ttc"],
"nb_lignes": resultat["nb_lignes"], "nb_lignes": resultat["nb_lignes"],
"reference": avoir.reference, "reference": resultat.get("reference"),
"date_livraison": resultat.get("date_livraison"),
}, },
} }
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error(f"Erreur création avoir: {e}") logger.error(f"Erreur création avoir: {e}")
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@ -2434,6 +2421,12 @@ async def lire_livraison(numero: str):
async def creer_livraison( async def creer_livraison(
livraison: LivraisonCreate, session: AsyncSession = Depends(get_session) livraison: LivraisonCreate, session: AsyncSession = Depends(get_session)
): ):
"""
Crée un bon de livraison dans Sage 100
- Le prix_unitaire_ht est optionnel (utilise le prix Sage si non fourni)
- La remise_pourcentage est appliquée après le prix
"""
try: try:
livraison_data = { livraison_data = {
"client_id": livraison.client_id, "client_id": livraison.client_id,
@ -2448,19 +2441,15 @@ async def creer_livraison(
else None else None
), ),
"reference": livraison.reference, "reference": livraison.reference,
"lignes": [ "lignes": _preparer_lignes_document(livraison.lignes),
{
"article_code": ligne.article_code,
"quantite": ligne.quantite,
"remise_pourcentage": ligne.remise_pourcentage,
}
for ligne in livraison.lignes
],
} }
resultat = sage_client.creer_livraison(livraison_data) resultat = sage_client.creer_livraison(livraison_data)
logger.info(f"Livraison créée: {resultat.get('numero_livraison')}") logger.info(
f"✅ Livraison créée: {resultat.get('numero_livraison')} "
f"({resultat.get('total_ttc')}€ TTC)"
)
return { return {
"success": True, "success": True,
@ -2469,17 +2458,18 @@ async def creer_livraison(
"numero_livraison": resultat["numero_livraison"], "numero_livraison": resultat["numero_livraison"],
"client_id": livraison.client_id, "client_id": livraison.client_id,
"date_livraison": resultat["date_livraison"], "date_livraison": resultat["date_livraison"],
"date_livraison_prevue": resultat.get("date_livraison_prevue"),
"total_ht": resultat["total_ht"], "total_ht": resultat["total_ht"],
"total_ttc": resultat["total_ttc"], "total_ttc": resultat["total_ttc"],
"nb_lignes": resultat["nb_lignes"], "nb_lignes": resultat["nb_lignes"],
"reference": livraison.reference, "reference": resultat.get("reference"),
}, },
} }
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error(f"Erreur création livraison: {e}") logger.error(f"Erreur création livraison: {e}")
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))

View file

@ -1,23 +1,14 @@
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field
from typing import List, Optional from typing import List, Optional
from datetime import date from datetime import date
from schemas.documents.ligne_document import LigneDocument
class LigneAvoir(BaseModel):
article_code: str
quantite: float
remise_pourcentage: Optional[float] = 0.0
@field_validator("article_code", mode="before")
def strip_insecables(cls, v):
return v.replace("\xa0", "").strip()
class AvoirCreate(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
lignes: List[LigneAvoir] lignes: List[LigneDocument]
reference: Optional[str] = None reference: Optional[str] = None
class Config: class Config:
@ -41,7 +32,7 @@ class AvoirCreate(BaseModel):
class AvoirUpdate(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[LigneDocument]] = None
statut: Optional[int] = Field(None, ge=0, le=6) statut: Optional[int] = Field(None, ge=0, le=6)
reference: Optional[str] = None reference: Optional[str] = None

View file

@ -1,23 +1,14 @@
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field
from typing import List, Optional from typing import List, Optional
from datetime import date from datetime import date
from schemas.documents.ligne_document import LigneDocument
class LigneCommande(BaseModel):
article_code: str
quantite: float
remise_pourcentage: Optional[float] = 0.0
@field_validator("article_code", mode="before")
def strip_insecables(cls, v):
return v.replace("\xa0", "").strip()
class CommandeCreate(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
lignes: List[LigneCommande] lignes: List[LigneDocument]
reference: Optional[str] = None reference: Optional[str] = None
class Config: class Config:
@ -41,7 +32,7 @@ class CommandeCreate(BaseModel):
class CommandeUpdate(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[LigneDocument]] = None
statut: Optional[int] = Field(None, ge=0, le=6) statut: Optional[int] = Field(None, ge=0, le=6)
reference: Optional[str] = None reference: Optional[str] = None

View file

@ -1,16 +1,8 @@
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field
from typing import List, Optional from typing import List, Optional
from datetime import date from datetime import date
from schemas.documents.ligne_document import LigneDocument
class LigneDevis(BaseModel):
article_code: str
quantite: float
remise_pourcentage: Optional[float] = 0.0
@field_validator("article_code", mode="before")
def strip_insecables(cls, v):
return v.replace("\xa0", "").strip()
class DevisRequest(BaseModel): class DevisRequest(BaseModel):
@ -18,7 +10,7 @@ class DevisRequest(BaseModel):
date_devis: Optional[date] = None date_devis: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
reference: Optional[str] = None reference: Optional[str] = None
lignes: List[LigneDevis] lignes: List[LigneDocument]
class Devis(BaseModel): class Devis(BaseModel):
@ -35,8 +27,8 @@ class DevisUpdate(BaseModel):
date_devis: Optional[date] = None date_devis: Optional[date] = None
date_livraison: Optional[date] = None date_livraison: Optional[date] = None
lignes: Optional[List[LigneDocument]] = None
reference: Optional[str] = None reference: Optional[str] = None
lignes: Optional[List[LigneDevis]] = None
statut: Optional[int] = Field(None, ge=0, le=6) statut: Optional[int] = Field(None, ge=0, le=6)
class Config: class Config:

View file

@ -1,23 +1,14 @@
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field
from typing import List, Optional from typing import List, Optional
from datetime import date from datetime import date
from schemas.documents.ligne_document import LigneDocument
class LigneFacture(BaseModel):
article_code: str
quantite: float
remise_pourcentage: Optional[float] = 0.0
@field_validator("article_code", mode="before")
def strip_insecables(cls, v):
return v.replace("\xa0", "").strip()
class FactureCreate(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
lignes: List[LigneFacture] lignes: List[LigneDocument]
reference: Optional[str] = None reference: Optional[str] = None
class Config: class Config:
@ -41,7 +32,7 @@ class FactureCreate(BaseModel):
class FactureUpdate(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[LigneDocument]] = None
statut: Optional[int] = Field(None, ge=0, le=6) statut: Optional[int] = Field(None, ge=0, le=6)
reference: Optional[str] = None reference: Optional[str] = None

View file

@ -0,0 +1,25 @@
from pydantic import BaseModel, field_validator
from typing import Optional
class LigneDocument(BaseModel):
article_code: str
quantite: float
prix_unitaire_ht: Optional[float] = None
remise_pourcentage: Optional[float] = 0.0
@field_validator("article_code", mode="before")
def strip_insecables(cls, v):
return v.replace("\xa0", "").strip()
@field_validator("quantite")
def validate_quantite(cls, v):
if v <= 0:
raise ValueError("La quantité doit être positive")
return v
@field_validator("remise_pourcentage")
def validate_remise(cls, v):
if v is not None and (v < 0 or v > 100):
raise ValueError("La remise doit être entre 0 et 100")
return v

View file

@ -1,23 +1,15 @@
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field
from typing import List, Optional from typing import List, Optional
from datetime import date from datetime import date
from schemas.documents.ligne_document import LigneDocument
class LigneLivraison(BaseModel):
article_code: str
quantite: float
remise_pourcentage: Optional[float] = 0.0
@field_validator("article_code", mode="before")
def strip_insecables(cls, v):
return v.replace("\xa0", "").strip()
class LivraisonCreate(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
lignes: List[LigneLivraison] lignes: List[LigneDocument]
reference: Optional[str] = None reference: Optional[str] = None
class Config: class Config:
@ -41,7 +33,7 @@ class LivraisonCreate(BaseModel):
class LivraisonUpdate(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[LigneDocument]] = None
statut: Optional[int] = Field(None, ge=0, le=6) statut: Optional[int] = Field(None, ge=0, le=6)
reference: Optional[str] = None reference: Optional[str] = None

View file

@ -1,6 +1,5 @@
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field, field_validator
from typing import List, Optional from typing import Optional
from schemas.tiers.contact import Contact
from schemas.tiers.tiers import TiersDetails from schemas.tiers.tiers import TiersDetails

View file

@ -1,4 +1,4 @@
from typing import Dict from typing import Dict, List
from config.config import settings from config.config import settings
import logging import logging
@ -264,3 +264,18 @@ async def universign_statut(transaction_id: str) -> Dict:
except Exception as e: except Exception as e:
logger.error(f"Erreur statut Universign: {e}") logger.error(f"Erreur statut Universign: {e}")
return {"statut": "ERREUR", "error": str(e)} return {"statut": "ERREUR", "error": str(e)}
def _preparer_lignes_document(lignes: List) -> List[Dict]:
return [
{
"article_code": ligne.article_code,
"quantite": ligne.quantite,
"prix_unitaire_ht": ligne.prix_unitaire_ht,
"remise_pourcentage": ligne.remise_pourcentage or 0.0,
}
for ligne in lignes
]
__all__ = ["_preparer_lignes_document"]