diff --git a/api.py b/api.py index 629e85f..9e62053 100644 --- a/api.py +++ b/api.py @@ -78,7 +78,7 @@ from core.sage_context import ( get_gateway_context_for_user, GatewayContext, ) - +from utils.generic_functions import _preparer_lignes_document if os.path.exists("/app"): 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 ), "reference": devis.reference, - "lignes": [ - { - "article_code": ligne.article_code, - "quantite": ligne.quantite, - "remise_pourcentage": ligne.remise_pourcentage, - } - for ligne in devis.lignes - ], + "lignes": _preparer_lignes_document(devis.lignes), } 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( id=resultat["numero_devis"], @@ -550,7 +546,7 @@ async def creer_devis(devis: DevisRequest): ) 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)) @@ -613,19 +609,15 @@ async def creer_commande( commande.date_livraison.isoformat() if commande.date_livraison else None ), "reference": commande.reference, - "lignes": [ - { - "article_code": ligne.article_code, - "quantite": ligne.quantite, - "remise_pourcentage": ligne.remise_pourcentage, - } - for ligne in commande.lignes - ], + "lignes": _preparer_lignes_document(commande.lignes), } 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 { "success": True, @@ -637,14 +629,15 @@ async def creer_commande( "total_ht": resultat["total_ht"], "total_ttc": resultat["total_ttc"], "nb_lignes": resultat["nb_lignes"], - "reference": commande.reference, + "reference": resultat.get("reference"), + "date_livraison": resultat.get("date_livraison"), }, } except HTTPException: raise 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)) @@ -1798,19 +1791,15 @@ async def creer_facture( facture.date_livraison.isoformat() if facture.date_livraison else None ), "reference": facture.reference, - "lignes": [ - { - "article_code": ligne.article_code, - "quantite": ligne.quantite, - "remise_pourcentage": ligne.remise_pourcentage, - } - for ligne in facture.lignes - ], + "lignes": _preparer_lignes_document(facture.lignes), } 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 { "success": True, @@ -1822,14 +1811,15 @@ async def creer_facture( "total_ht": resultat["total_ht"], "total_ttc": resultat["total_ttc"], "nb_lignes": resultat["nb_lignes"], - "reference": facture.reference, + "reference": resultat.get("reference"), + "date_livraison": resultat.get("date_livraison"), }, } except HTTPException: raise 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)) @@ -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 ), "reference": avoir.reference, - "lignes": [ - { - "article_code": ligne.article_code, - "quantite": ligne.quantite, - "remise_pourcentage": ligne.remise_pourcentage, - } - for ligne in avoir.lignes - ], + "lignes": _preparer_lignes_document(avoir.lignes), } 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 { "success": True, @@ -2348,14 +2334,15 @@ async def creer_avoir(avoir: AvoirCreate, session: AsyncSession = Depends(get_se "total_ht": resultat["total_ht"], "total_ttc": resultat["total_ttc"], "nb_lignes": resultat["nb_lignes"], - "reference": avoir.reference, + "reference": resultat.get("reference"), + "date_livraison": resultat.get("date_livraison"), }, } except HTTPException: raise 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)) @@ -2434,6 +2421,12 @@ async def lire_livraison(numero: str): async def creer_livraison( 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: livraison_data = { "client_id": livraison.client_id, @@ -2448,19 +2441,15 @@ async def creer_livraison( else None ), "reference": livraison.reference, - "lignes": [ - { - "article_code": ligne.article_code, - "quantite": ligne.quantite, - "remise_pourcentage": ligne.remise_pourcentage, - } - for ligne in livraison.lignes - ], + "lignes": _preparer_lignes_document(livraison.lignes), } 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 { "success": True, @@ -2469,17 +2458,18 @@ async def creer_livraison( "numero_livraison": resultat["numero_livraison"], "client_id": livraison.client_id, "date_livraison": resultat["date_livraison"], + "date_livraison_prevue": resultat.get("date_livraison_prevue"), "total_ht": resultat["total_ht"], "total_ttc": resultat["total_ttc"], "nb_lignes": resultat["nb_lignes"], - "reference": livraison.reference, + "reference": resultat.get("reference"), }, } except HTTPException: raise 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)) diff --git a/schemas/documents/avoirs.py b/schemas/documents/avoirs.py index adc0aa4..9397b74 100644 --- a/schemas/documents/avoirs.py +++ b/schemas/documents/avoirs.py @@ -1,23 +1,14 @@ -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field from typing import List, Optional from datetime import date - -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() - +from schemas.documents.ligne_document import LigneDocument class AvoirCreate(BaseModel): client_id: str date_avoir: Optional[date] = None date_livraison: Optional[date] = None - lignes: List[LigneAvoir] + lignes: List[LigneDocument] reference: Optional[str] = None class Config: @@ -41,7 +32,7 @@ class AvoirCreate(BaseModel): class AvoirUpdate(BaseModel): date_avoir: 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) reference: Optional[str] = None diff --git a/schemas/documents/commandes.py b/schemas/documents/commandes.py index 792c690..9777fe3 100644 --- a/schemas/documents/commandes.py +++ b/schemas/documents/commandes.py @@ -1,23 +1,14 @@ -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field from typing import List, Optional from datetime import date - -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() - +from schemas.documents.ligne_document import LigneDocument class CommandeCreate(BaseModel): client_id: str date_commande: Optional[date] = None date_livraison: Optional[date] = None - lignes: List[LigneCommande] + lignes: List[LigneDocument] reference: Optional[str] = None class Config: @@ -41,7 +32,7 @@ class CommandeCreate(BaseModel): class CommandeUpdate(BaseModel): date_commande: 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) reference: Optional[str] = None diff --git a/schemas/documents/devis.py b/schemas/documents/devis.py index 2c1ed5a..7dfedd5 100644 --- a/schemas/documents/devis.py +++ b/schemas/documents/devis.py @@ -1,16 +1,8 @@ -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field from typing import List, Optional from datetime import date - -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() +from schemas.documents.ligne_document import LigneDocument class DevisRequest(BaseModel): @@ -18,7 +10,7 @@ class DevisRequest(BaseModel): date_devis: Optional[date] = None date_livraison: Optional[date] = None reference: Optional[str] = None - lignes: List[LigneDevis] + lignes: List[LigneDocument] class Devis(BaseModel): @@ -35,8 +27,8 @@ class DevisUpdate(BaseModel): date_devis: Optional[date] = None date_livraison: Optional[date] = None + lignes: Optional[List[LigneDocument]] = None reference: Optional[str] = None - lignes: Optional[List[LigneDevis]] = None statut: Optional[int] = Field(None, ge=0, le=6) class Config: diff --git a/schemas/documents/factures.py b/schemas/documents/factures.py index cf8b62b..5e91ac8 100644 --- a/schemas/documents/factures.py +++ b/schemas/documents/factures.py @@ -1,23 +1,14 @@ -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field from typing import List, Optional from datetime import date - -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() - +from schemas.documents.ligne_document import LigneDocument class FactureCreate(BaseModel): client_id: str date_facture: Optional[date] = None date_livraison: Optional[date] = None - lignes: List[LigneFacture] + lignes: List[LigneDocument] reference: Optional[str] = None class Config: @@ -41,7 +32,7 @@ class FactureCreate(BaseModel): class FactureUpdate(BaseModel): date_facture: 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) reference: Optional[str] = None diff --git a/schemas/documents/ligne_document.py b/schemas/documents/ligne_document.py new file mode 100644 index 0000000..4666ace --- /dev/null +++ b/schemas/documents/ligne_document.py @@ -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 diff --git a/schemas/documents/livraisons.py b/schemas/documents/livraisons.py index d8c3589..5e39e46 100644 --- a/schemas/documents/livraisons.py +++ b/schemas/documents/livraisons.py @@ -1,23 +1,15 @@ -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field from typing import List, Optional from datetime import date - -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() +from schemas.documents.ligne_document import LigneDocument class LivraisonCreate(BaseModel): client_id: str date_livraison: Optional[date] = None date_livraison_prevue: Optional[date] = None - lignes: List[LigneLivraison] + lignes: List[LigneDocument] reference: Optional[str] = None class Config: @@ -41,7 +33,7 @@ class LivraisonCreate(BaseModel): class LivraisonUpdate(BaseModel): date_livraison: 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) reference: Optional[str] = None diff --git a/schemas/tiers/clients.py b/schemas/tiers/clients.py index f4bb35a..8085375 100644 --- a/schemas/tiers/clients.py +++ b/schemas/tiers/clients.py @@ -1,6 +1,5 @@ from pydantic import BaseModel, Field, field_validator -from typing import List, Optional -from schemas.tiers.contact import Contact +from typing import Optional from schemas.tiers.tiers import TiersDetails diff --git a/utils/generic_functions.py b/utils/generic_functions.py index 157e483..221d5b7 100644 --- a/utils/generic_functions.py +++ b/utils/generic_functions.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Dict, List from config.config import settings import logging @@ -264,3 +264,18 @@ async def universign_statut(transaction_id: str) -> Dict: except Exception as e: logger.error(f"Erreur statut Universign: {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"]