Restructured files and reorganization for better consistency
This commit is contained in:
parent
0e2398278f
commit
ffb7a50443
21 changed files with 3863 additions and 3334 deletions
377
data/data.py
Normal file
377
data/data.py
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
TAGS_METADATA = [
|
||||
{
|
||||
"name": "Clients",
|
||||
"description": "Gestion des clients (recherche, création, modification)",
|
||||
},
|
||||
{"name": "Articles", "description": "Gestion des articles et produits"},
|
||||
{"name": "Devis", "description": "Création, consultation et gestion des devis"},
|
||||
{
|
||||
"name": "Commandes",
|
||||
"description": "Création, consultation et gestion des commandes",
|
||||
},
|
||||
{
|
||||
"name": "Livraisons",
|
||||
"description": "Création, consultation et gestion des bons de livraison",
|
||||
},
|
||||
{
|
||||
"name": "Factures",
|
||||
"description": "Création, consultation et gestion des factures",
|
||||
},
|
||||
{"name": "Avoirs", "description": "Création, consultation et gestion des avoirs"},
|
||||
{"name": "Fournisseurs", "description": "Gestion des fournisseurs"},
|
||||
{"name": "Prospects", "description": "Gestion des prospects"},
|
||||
{
|
||||
"name": "Workflows",
|
||||
"description": "Transformations de documents (devis→commande, commande→facture, etc.)",
|
||||
},
|
||||
{"name": "Signatures", "description": "Signature électronique via Universign"},
|
||||
{"name": "Emails", "description": "Envoi d'emails, templates et logs d'envoi"},
|
||||
{"name": "Validation", "description": "Validation de données (remises, etc.)"},
|
||||
{"name": "Admin", "description": "🔧 Administration système (cache, queue)"},
|
||||
{"name": "System", "description": "🏥 Health checks et informations système"},
|
||||
{"name": "Debug", "description": "🐛 Routes de debug et diagnostics"},
|
||||
]
|
||||
|
||||
templates_signature_email = {
|
||||
"demande_signature": {
|
||||
"id": "demande_signature",
|
||||
"nom": "Demande de Signature Électronique",
|
||||
"sujet": "Signature requise - {{TYPE_DOC}} {{NUMERO}}",
|
||||
"corps_html": """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: #f4f7fa;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f7fa; padding: 40px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 8px 8px 0 0;">
|
||||
<h1 style="color: #000; margin: 0; font-size: 24px; font-weight: 600;">
|
||||
Signature Électronique Requise
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<p style="color: #2d3748; font-size: 16px; line-height: 1.6; margin: 0 0 20px;">
|
||||
Bonjour <strong>{{NOM_SIGNATAIRE}}</strong>,
|
||||
</p>
|
||||
|
||||
<p style="color: #4a5568; font-size: 15px; line-height: 1.6; margin: 0 0 25px;">
|
||||
Nous vous invitons à signer électroniquement le document suivant :
|
||||
</p>
|
||||
|
||||
<!-- Document Info Box -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f7fafc; border-left: 4px solid #667eea; border-radius: 4px; margin-bottom: 30px;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td style="color: #718096; font-size: 13px; padding: 5px 0;">Type de document</td>
|
||||
<td style="color: #2d3748; font-size: 15px; font-weight: 600; text-align: right; padding: 5px 0;">{{TYPE_DOC}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #718096; font-size: 13px; padding: 5px 0;">Numéro</td>
|
||||
<td style="color: #2d3748; font-size: 15px; font-weight: 600; text-align: right; padding: 5px 0;">{{NUMERO}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #718096; font-size: 13px; padding: 5px 0;">Date</td>
|
||||
<td style="color: #2d3748; font-size: 15px; font-weight: 600; text-align: right; padding: 5px 0;">{{DATE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #718096; font-size: 13px; padding: 5px 0;">Montant TTC</td>
|
||||
<td style="color: #2d3748; font-size: 15px; font-weight: 600; text-align: right; padding: 5px 0;">{{MONTANT_TTC}} €</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #4a5568; font-size: 15px; line-height: 1.6; margin: 0 0 30px;">
|
||||
Cliquez sur le bouton ci-dessous pour accéder au document et apposer votre signature électronique sécurisée :
|
||||
</p>
|
||||
|
||||
<!-- CTA Button -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 10px 0 30px;">
|
||||
<a href="{{SIGNER_URL}}" style="display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #000; text-decoration: none; padding: 16px 40px; border-radius: 6px; font-size: 16px; font-weight: 600; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);">
|
||||
✍️ Signer le document
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Info Box -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #fffaf0; border: 1px solid #fbd38d; border-radius: 4px; margin-bottom: 20px;">
|
||||
<tr>
|
||||
<td style="padding: 15px;">
|
||||
<p style="color: #744210; font-size: 13px; line-height: 1.5; margin: 0;">
|
||||
⏰ <strong>Important :</strong> Ce lien de signature est valable pendant <strong>30 jours</strong>.
|
||||
Nous vous recommandons de signer ce document dès que possible.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #718096; font-size: 13px; line-height: 1.5; margin: 0;">
|
||||
<strong>🔒 Signature électronique sécurisée</strong><br>
|
||||
Votre signature est protégée par notre partenaire de confiance <strong>Universign</strong>,
|
||||
certifié eIDAS et conforme au RGPD. Votre identité sera vérifiée et le document sera
|
||||
horodaté de manière infalsifiable.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f7fafc; padding: 25px 30px; border-radius: 0 0 8px 8px; border-top: 1px solid #e2e8f0;">
|
||||
<p style="color: #718096; font-size: 12px; line-height: 1.5; margin: 0 0 10px;">
|
||||
Vous avez des questions ? Contactez-nous à <a href="mailto:{{CONTACT_EMAIL}}" style="color: #667eea; text-decoration: none;">{{CONTACT_EMAIL}}</a>
|
||||
</p>
|
||||
<p style="color: #a0aec0; font-size: 11px; line-height: 1.4; margin: 0;">
|
||||
Cet email a été envoyé automatiquement par le système Sage 100c Dataven.<br>
|
||||
Si vous avez reçu cet email par erreur, veuillez nous en informer.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
"variables_disponibles": [
|
||||
"NOM_SIGNATAIRE",
|
||||
"TYPE_DOC",
|
||||
"NUMERO",
|
||||
"DATE",
|
||||
"MONTANT_TTC",
|
||||
"SIGNER_URL",
|
||||
"CONTACT_EMAIL",
|
||||
],
|
||||
},
|
||||
"signature_confirmee": {
|
||||
"id": "signature_confirmee",
|
||||
"nom": "Confirmation de Signature",
|
||||
"sujet": "Document signé - {{TYPE_DOC}} {{NUMERO}}",
|
||||
"corps_html": """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: #f4f7fa;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f7fa; padding: 40px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); padding: 30px; text-align: center; border-radius: 8px 8px 0 0;">
|
||||
<h1 style="color: #ffffff; margin: 0; font-size: 24px; font-weight: 600;">
|
||||
Document Signé avec Succès
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<p style="color: #2d3748; font-size: 16px; line-height: 1.6; margin: 0 0 20px;">
|
||||
Bonjour <strong>{{NOM_SIGNATAIRE}}</strong>,
|
||||
</p>
|
||||
|
||||
<p style="color: #4a5568; font-size: 15px; line-height: 1.6; margin: 0 0 25px;">
|
||||
Nous confirmons la signature électronique du document suivant :
|
||||
</p>
|
||||
|
||||
<!-- Success Box -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f0fff4; border-left: 4px solid #48bb78; border-radius: 4px; margin-bottom: 30px;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td style="color: #2f855a; font-size: 13px; padding: 5px 0;">Document</td>
|
||||
<td style="color: #22543d; font-size: 15px; font-weight: 600; text-align: right; padding: 5px 0;">{{TYPE_DOC}} {{NUMERO}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #2f855a; font-size: 13px; padding: 5px 0;">Signé le</td>
|
||||
<td style="color: #22543d; font-size: 15px; font-weight: 600; text-align: right; padding: 5px 0;">{{DATE_SIGNATURE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #2f855a; font-size: 13px; padding: 5px 0;">ID Transaction</td>
|
||||
<td style="color: #22543d; font-size: 13px; font-family: monospace; text-align: right; padding: 5px 0;">{{TRANSACTION_ID}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #4a5568; font-size: 15px; line-height: 1.6; margin: 0 0 25px;">
|
||||
Le document signé a été automatiquement archivé et est disponible dans votre espace client.
|
||||
Un certificat de signature électronique conforme eIDAS a été généré.
|
||||
</p>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #ebf8ff; border: 1px solid #90cdf4; border-radius: 4px; margin-bottom: 20px;">
|
||||
<tr>
|
||||
<td style="padding: 15px;">
|
||||
<p style="color: #2c5282; font-size: 13px; line-height: 1.5; margin: 0;">
|
||||
<strong>Signature certifiée :</strong> Ce document a été signé avec une signature
|
||||
électronique qualifiée, ayant la même valeur juridique qu'une signature manuscrite
|
||||
conformément au règlement eIDAS.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #718096; font-size: 14px; line-height: 1.6; margin: 0;">
|
||||
Merci pour votre confiance. Notre équipe reste à votre disposition pour toute question.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f7fafc; padding: 25px 30px; border-radius: 0 0 8px 8px; border-top: 1px solid #e2e8f0;">
|
||||
<p style="color: #718096; font-size: 12px; line-height: 1.5; margin: 0 0 10px;">
|
||||
Contact : <a href="mailto:{{CONTACT_EMAIL}}" style="color: #48bb78; text-decoration: none;">{{CONTACT_EMAIL}}</a>
|
||||
</p>
|
||||
<p style="color: #a0aec0; font-size: 11px; line-height: 1.4; margin: 0;">
|
||||
Sage 100c Dataven - Système de signature électronique sécurisée
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
"variables_disponibles": [
|
||||
"NOM_SIGNATAIRE",
|
||||
"TYPE_DOC",
|
||||
"NUMERO",
|
||||
"DATE_SIGNATURE",
|
||||
"TRANSACTION_ID",
|
||||
"CONTACT_EMAIL",
|
||||
],
|
||||
},
|
||||
"relance_signature": {
|
||||
"id": "relance_signature",
|
||||
"nom": "Relance Signature en Attente",
|
||||
"sujet": "⏰ Rappel - Signature en attente {{TYPE_DOC}} {{NUMERO}}",
|
||||
"corps_html": """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: #f4f7fa;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f7fa; padding: 40px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%); padding: 30px; text-align: center; border-radius: 8px 8px 0 0;">
|
||||
<h1 style="color: #ffffff; margin: 0; font-size: 24px; font-weight: 600;">
|
||||
⏰ Signature en Attente
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<p style="color: #2d3748; font-size: 16px; line-height: 1.6; margin: 0 0 20px;">
|
||||
Bonjour <strong>{{NOM_SIGNATAIRE}}</strong>,
|
||||
</p>
|
||||
|
||||
<p style="color: #4a5568; font-size: 15px; line-height: 1.6; margin: 0 0 25px;">
|
||||
Nous vous avons envoyé il y a <strong>{{NB_JOURS}}</strong> jours un document à signer électroniquement.
|
||||
Nous constatons que celui-ci n'a pas encore été signé.
|
||||
</p>
|
||||
|
||||
<!-- Warning Box -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #fffaf0; border-left: 4px solid #ed8936; border-radius: 4px; margin-bottom: 30px;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
<p style="color: #744210; font-size: 14px; line-height: 1.5; margin: 0 0 10px;">
|
||||
<strong>Document en attente :</strong> {{TYPE_DOC}} {{NUMERO}}
|
||||
</p>
|
||||
<p style="color: #744210; font-size: 13px; line-height: 1.5; margin: 0;">
|
||||
⏳ Le lien de signature expirera dans <strong>{{JOURS_RESTANTS}}</strong> jours
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #4a5568; font-size: 15px; line-height: 1.6; margin: 0 0 30px;">
|
||||
Pour éviter tout retard dans le traitement de votre dossier, nous vous invitons à signer ce document dès maintenant :
|
||||
</p>
|
||||
|
||||
<!-- CTA Button -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 10px 0 30px;">
|
||||
<a href="{{SIGNER_URL}}" style="display: inline-block; background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%); color: #ffffff; text-decoration: none; padding: 16px 40px; border-radius: 6px; font-size: 16px; font-weight: 600; box-shadow: 0 4px 12px rgba(237, 137, 54, 0.4);">
|
||||
✍️ Signer maintenant
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #718096; font-size: 13px; line-height: 1.5; margin: 0;">
|
||||
Si vous rencontrez des difficultés ou avez des questions, n'hésitez pas à nous contacter.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f7fafc; padding: 25px 30px; border-radius: 0 0 8px 8px; border-top: 1px solid #e2e8f0;">
|
||||
<p style="color: #718096; font-size: 12px; line-height: 1.5; margin: 0 0 10px;">
|
||||
Contact : <a href="mailto:{{CONTACT_EMAIL}}" style="color: #ed8936; text-decoration: none;">{{CONTACT_EMAIL}}</a>
|
||||
</p>
|
||||
<p style="color: #a0aec0; font-size: 11px; line-height: 1.4; margin: 0;">
|
||||
Sage 100c Dataven - Relance automatique
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
"variables_disponibles": [
|
||||
"NOM_SIGNATAIRE",
|
||||
"TYPE_DOC",
|
||||
"NUMERO",
|
||||
"NB_JOURS",
|
||||
"JOURS_RESTANTS",
|
||||
"SIGNER_URL",
|
||||
"CONTACT_EMAIL",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
@ -1,7 +1,118 @@
|
|||
from schemas.tiers.tiers import (TiersDetails,)
|
||||
from schemas.tiers.type_tiers import (TypeTiers,)
|
||||
from schemas.tiers.tiers import (
|
||||
TiersDetails,
|
||||
TypeTiersInt
|
||||
)
|
||||
from schemas.tiers.type_tiers import TypeTiers
|
||||
from schemas.schema_mixte import BaremeRemiseResponse
|
||||
from schemas.user import UserResponse
|
||||
from schemas.tiers.clients import (
|
||||
ClientCreateRequest,
|
||||
ClientDetails,
|
||||
ClientResponse,
|
||||
ClientUpdateRequest
|
||||
)
|
||||
from schemas.tiers.contact import (
|
||||
Contact,
|
||||
ContactCreate,
|
||||
ContactUpdate
|
||||
)
|
||||
from schemas.tiers.fournisseurs import (
|
||||
FournisseurCreateAPIRequest,
|
||||
FournisseurDetails,
|
||||
FournisseurUpdateRequest
|
||||
)
|
||||
from schemas.documents.avoirs import (
|
||||
AvoirCreateRequest,
|
||||
AvoirUpdateRequest
|
||||
)
|
||||
from schemas.documents.commandes import (
|
||||
CommandeCreateRequest,
|
||||
CommandeUpdateRequest
|
||||
)
|
||||
from schemas.documents.devis import (
|
||||
DevisRequest,
|
||||
DevisResponse,
|
||||
DevisUpdateRequest,
|
||||
RelanceDevisRequest
|
||||
)
|
||||
from schemas.documents.documents import (
|
||||
TypeDocument,
|
||||
TypeDocumentSQL
|
||||
)
|
||||
from schemas.documents.email import (
|
||||
StatutEmail,
|
||||
EmailEnvoiRequest
|
||||
)
|
||||
from schemas.documents.factures import (
|
||||
FactureCreateRequest,
|
||||
FactureUpdateRequest
|
||||
)
|
||||
from schemas.documents.livraisons import (
|
||||
LivraisonCreateRequest,
|
||||
LivraisonUpdateRequest
|
||||
)
|
||||
from schemas.documents.universign import (
|
||||
SignatureRequest,
|
||||
StatutSignature
|
||||
)
|
||||
from schemas.articles.articles import (
|
||||
ArticleCreateRequest,
|
||||
ArticleResponse,
|
||||
ArticleUpdateRequest,
|
||||
ArticleListResponse,
|
||||
EntreeStockRequest,
|
||||
SortieStockRequest,
|
||||
MouvementStockResponse
|
||||
)
|
||||
from schemas.articles.famille_article import (
|
||||
FamilleResponse,
|
||||
FamilleCreateRequest,
|
||||
FamilleListResponse
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TiersDetails",
|
||||
"TypeTiers",
|
||||
"BaremeRemiseResponse",
|
||||
"UserResponse",
|
||||
"ClientCreateRequest",
|
||||
"ClientDetails",
|
||||
"ClientResponse",
|
||||
"ClientUpdateRequest",
|
||||
"FournisseurCreateAPIRequest",
|
||||
"FournisseurDetails",
|
||||
"FournisseurUpdateRequest",
|
||||
"Contact",
|
||||
"AvoirCreateRequest",
|
||||
"AvoirUpdateRequest",
|
||||
"CommandeCreateRequest",
|
||||
"CommandeUpdateRequest",
|
||||
"DevisRequest",
|
||||
"DevisResponse",
|
||||
"DevisUpdateRequest",
|
||||
"TypeDocument",
|
||||
"TypeDocumentSQL",
|
||||
"StatutEmail",
|
||||
"EmailEnvoiRequest",
|
||||
"FactureCreateRequest",
|
||||
"FactureUpdateRequest",
|
||||
"LivraisonCreateRequest",
|
||||
"LivraisonUpdateRequest",
|
||||
"SignatureRequest",
|
||||
"StatutSignature",
|
||||
"TypeTiersInt",
|
||||
"ArticleCreateRequest",
|
||||
"ArticleResponse",
|
||||
"ArticleUpdateRequest",
|
||||
"ArticleListResponse",
|
||||
"EntreeStockRequest",
|
||||
"SortieStockRequest",
|
||||
"MouvementStockResponse",
|
||||
"RelanceDevisRequest",
|
||||
"FamilleResponse",
|
||||
"FamilleCreateRequest",
|
||||
"FamilleListResponse",
|
||||
"ContactCreate",
|
||||
"ContactUpdate"
|
||||
]
|
||||
948
schemas/articles/articles.py
Normal file
948
schemas/articles/articles.py
Normal file
|
|
@ -0,0 +1,948 @@
|
|||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
|
||||
class EmplacementStockModel(BaseModel):
|
||||
"""Détail du stock dans un emplacement spécifique"""
|
||||
|
||||
depot: str = Field(..., description="Numéro du dépôt (DE_No)")
|
||||
emplacement: str = Field(..., description="Code emplacement (DP_No)")
|
||||
|
||||
qte_stockee: float = Field(0.0, description="Quantité stockée (AE_QteSto)")
|
||||
qte_preparee: float = Field(0.0, description="Quantité préparée (AE_QtePrepa)")
|
||||
qte_a_controler: float = Field(
|
||||
0.0, description="Quantité à contrôler (AE_QteAControler)"
|
||||
)
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
depot_num: Optional[str] = Field(None, description="Numéro dépôt")
|
||||
depot_nom: Optional[str] = Field(None, description="Nom du dépôt (DE_Intitule)")
|
||||
depot_code: Optional[str] = Field(None, description="Code dépôt (DE_Code)")
|
||||
depot_adresse: Optional[str] = Field(None, description="Adresse (DE_Adresse)")
|
||||
depot_complement: Optional[str] = Field(None, description="Complément adresse")
|
||||
depot_code_postal: Optional[str] = Field(None, description="Code postal")
|
||||
depot_ville: Optional[str] = Field(None, description="Ville")
|
||||
depot_contact: Optional[str] = Field(None, description="Contact")
|
||||
depot_est_principal: Optional[bool] = Field(
|
||||
None, description="Dépôt principal (DE_Principal)"
|
||||
)
|
||||
depot_categorie_compta: Optional[int] = Field(
|
||||
None, description="Catégorie comptable"
|
||||
)
|
||||
depot_region: Optional[str] = Field(None, description="Région")
|
||||
depot_pays: Optional[str] = Field(None, description="Pays")
|
||||
depot_email: Optional[str] = Field(None, description="Email")
|
||||
depot_telephone: Optional[str] = Field(None, description="Téléphone")
|
||||
depot_fax: Optional[str] = Field(None, description="Fax")
|
||||
depot_emplacement_defaut: Optional[str] = Field(
|
||||
None, description="Emplacement par défaut"
|
||||
)
|
||||
depot_exclu: Optional[bool] = Field(None, description="Dépôt exclu")
|
||||
|
||||
emplacement_code: Optional[str] = Field(
|
||||
None, description="Code emplacement (DP_Code)"
|
||||
)
|
||||
emplacement_libelle: Optional[str] = Field(
|
||||
None, description="Libellé emplacement (DP_Intitule)"
|
||||
)
|
||||
emplacement_zone: Optional[str] = Field(None, description="Zone (DP_Zone)")
|
||||
emplacement_type: Optional[int] = Field(
|
||||
None, description="Type emplacement (DP_Type)"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"depot": "01",
|
||||
"emplacement": "A1-01",
|
||||
"qte_stockee": 100.0,
|
||||
"qte_preparee": 5.0,
|
||||
"depot_nom": "Dépôt principal",
|
||||
"depot_ville": "Paris",
|
||||
"emplacement_libelle": "Allée A, Niveau 1, Case 01",
|
||||
"emplacement_zone": "Zone A",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GammeArticleModel(BaseModel):
|
||||
"""Gamme d'un article (taille, couleur, etc.)"""
|
||||
|
||||
numero_gamme: int = Field(..., description="Numéro de gamme (AG_No)")
|
||||
enumere: str = Field(..., description="Code énuméré (EG_Enumere)")
|
||||
type_gamme: int = Field(0, description="Type de gamme (AG_Type)")
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
ligne: Optional[int] = Field(None, description="Ligne énuméré (EG_Ligne)")
|
||||
borne_sup: Optional[float] = Field(
|
||||
None, description="Borne supérieure (EG_BorneSup)"
|
||||
)
|
||||
gamme_nom: Optional[str] = Field(
|
||||
None, description="Nom de la gamme (P_GAMME.G_Intitule)"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"numero_gamme": 1,
|
||||
"enumere": "001",
|
||||
"type_gamme": 0,
|
||||
"ligne": 1,
|
||||
"gamme_nom": "Taille",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TarifClientModel(BaseModel):
|
||||
"""Tarif spécifique pour un client ou catégorie tarifaire"""
|
||||
|
||||
categorie: int = Field(..., description="Catégorie tarifaire (AC_Categorie)")
|
||||
client_num: Optional[str] = Field(None, description="Numéro client (CT_Num)")
|
||||
|
||||
prix_vente: float = Field(0.0, description="Prix de vente HT (AC_PrixVen)")
|
||||
coefficient: float = Field(0.0, description="Coefficient (AC_Coef)")
|
||||
prix_ttc: float = Field(0.0, description="Prix TTC (AC_PrixTTC)")
|
||||
arrondi: float = Field(0.0, description="Arrondi (AC_Arrondi)")
|
||||
qte_montant: float = Field(0.0, description="Quantité montant (AC_QteMont)")
|
||||
|
||||
enumere_gamme: int = Field(0, description="Énuméré gamme (EG_Champ)")
|
||||
prix_devise: float = Field(0.0, description="Prix en devise (AC_PrixDev)")
|
||||
devise: int = Field(0, description="Code devise (AC_Devise)")
|
||||
|
||||
remise: float = Field(0.0, description="Remise (AC_Remise)")
|
||||
mode_calcul: int = Field(0, description="Mode de calcul (AC_Calcul)")
|
||||
type_remise: int = Field(0, description="Type de remise (AC_TypeRem)")
|
||||
ref_client: Optional[str] = Field(
|
||||
None, description="Référence client (AC_RefClient)"
|
||||
)
|
||||
|
||||
coef_nouveau: float = Field(0.0, description="Nouveau coefficient (AC_CoefNouv)")
|
||||
prix_vente_nouveau: float = Field(
|
||||
0.0, description="Nouveau prix vente (AC_PrixVenNouv)"
|
||||
)
|
||||
prix_devise_nouveau: float = Field(
|
||||
0.0, description="Nouveau prix devise (AC_PrixDevNouv)"
|
||||
)
|
||||
remise_nouvelle: float = Field(0.0, description="Nouvelle remise (AC_RemiseNouv)")
|
||||
date_application: Optional[datetime] = Field(
|
||||
None, description="Date application (AC_DateApplication)"
|
||||
)
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"categorie": 1,
|
||||
"client_num": "CLI001",
|
||||
"prix_vente": 110.00,
|
||||
"coefficient": 1.294,
|
||||
"remise": 12.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ComposantModel(BaseModel):
|
||||
"""Composant/Opération de nomenclature"""
|
||||
|
||||
operation: str = Field(..., description="Code opération (AT_Operation)")
|
||||
code_ressource: Optional[str] = Field(None, description="Code ressource (RP_Code)")
|
||||
|
||||
temps: float = Field(0.0, description="Temps nécessaire (AT_Temps)")
|
||||
type: int = Field(0, description="Type composant (AT_Type)")
|
||||
description: Optional[str] = Field(None, description="Description (AT_Description)")
|
||||
ordre: int = Field(0, description="Ordre d'exécution (AT_Ordre)")
|
||||
|
||||
gamme_1_comp: int = Field(0, description="Gamme 1 composant (AG_No1Comp)")
|
||||
gamme_2_comp: int = Field(0, description="Gamme 2 composant (AG_No2Comp)")
|
||||
|
||||
type_ressource: int = Field(0, description="Type ressource (AT_TypeRessource)")
|
||||
chevauche: int = Field(0, description="Chevauchement (AT_Chevauche)")
|
||||
demarre: int = Field(0, description="Démarrage (AT_Demarre)")
|
||||
operation_chevauche: Optional[str] = Field(
|
||||
None, description="Opération chevauchée (AT_OperationChevauche)"
|
||||
)
|
||||
valeur_chevauche: float = Field(
|
||||
0.0, description="Valeur chevauchement (AT_ValeurChevauche)"
|
||||
)
|
||||
type_chevauche: int = Field(0, description="Type chevauchement (AT_TypeChevauche)")
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"operation": "OP010",
|
||||
"code_ressource": "RES01",
|
||||
"temps": 15.5,
|
||||
"description": "Montage pièce A",
|
||||
"ordre": 10,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ComptaArticleModel(BaseModel):
|
||||
"""Comptabilité spécifique d'un article"""
|
||||
|
||||
champ: int = Field(..., description="Champ (ACP_Champ)")
|
||||
compte_general: Optional[str] = Field(
|
||||
None, description="Compte général (ACP_ComptaCPT_CompteG)"
|
||||
)
|
||||
compte_auxiliaire: Optional[str] = Field(
|
||||
None, description="Compte auxiliaire (ACP_ComptaCPT_CompteA)"
|
||||
)
|
||||
|
||||
taxe_1: Optional[str] = Field(None, description="Taxe 1 (ACP_ComptaCPT_Taxe1)")
|
||||
taxe_2: Optional[str] = Field(None, description="Taxe 2 (ACP_ComptaCPT_Taxe2)")
|
||||
taxe_3: Optional[str] = Field(None, description="Taxe 3 (ACP_ComptaCPT_Taxe3)")
|
||||
|
||||
taxe_date_1: Optional[datetime] = Field(None, description="Date taxe 1")
|
||||
taxe_date_2: Optional[datetime] = Field(None, description="Date taxe 2")
|
||||
taxe_date_3: Optional[datetime] = Field(None, description="Date taxe 3")
|
||||
|
||||
taxe_anc_1: Optional[str] = Field(None, description="Ancienne taxe 1")
|
||||
taxe_anc_2: Optional[str] = Field(None, description="Ancienne taxe 2")
|
||||
taxe_anc_3: Optional[str] = Field(None, description="Ancienne taxe 3")
|
||||
|
||||
type_facture: int = Field(0, description="Type de facture (ACP_TypeFacture)")
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"champ": 1,
|
||||
"compte_general": "707100",
|
||||
"taxe_1": "TVA20",
|
||||
"type_facture": 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FournisseurArticleModel(BaseModel):
|
||||
"""Fournisseur d'un article"""
|
||||
|
||||
fournisseur_num: str = Field(..., description="Numéro fournisseur (CT_Num)")
|
||||
ref_fournisseur: Optional[str] = Field(
|
||||
None, description="Référence fournisseur (AF_RefFourniss)"
|
||||
)
|
||||
|
||||
prix_achat: float = Field(0.0, description="Prix d'achat (AF_PrixAch)")
|
||||
unite: Optional[str] = Field(None, description="Unité (AF_Unite)")
|
||||
conversion: float = Field(0.0, description="Conversion (AF_Conversion)")
|
||||
|
||||
delai_appro: int = Field(0, description="Délai approvisionnement (AF_DelaiAppro)")
|
||||
garantie: int = Field(0, description="Garantie (AF_Garantie)")
|
||||
colisage: int = Field(0, description="Colisage (AF_Colisage)")
|
||||
qte_mini: float = Field(0.0, description="Quantité minimum (AF_QteMini)")
|
||||
qte_montant: float = Field(0.0, description="Quantité montant (AF_QteMont)")
|
||||
|
||||
enumere_gamme: int = Field(0, description="Énuméré gamme (EG_Champ)")
|
||||
est_principal: bool = Field(
|
||||
False, description="Fournisseur principal (AF_Principal)"
|
||||
)
|
||||
|
||||
prix_devise: float = Field(0.0, description="Prix devise (AF_PrixDev)")
|
||||
devise: int = Field(0, description="Code devise (AF_Devise)")
|
||||
remise: float = Field(0.0, description="Remise (AF_Remise)")
|
||||
conversion_devise: float = Field(0.0, description="Conversion devise (AF_ConvDiv)")
|
||||
type_remise: int = Field(0, description="Type remise (AF_TypeRem)")
|
||||
|
||||
code_barre_fournisseur: Optional[str] = Field(
|
||||
None, description="Code-barres fournisseur (AF_CodeBarre)"
|
||||
)
|
||||
|
||||
prix_achat_nouveau: float = Field(
|
||||
0.0, description="Nouveau prix achat (AF_PrixAchNouv)"
|
||||
)
|
||||
prix_devise_nouveau: float = Field(
|
||||
0.0, description="Nouveau prix devise (AF_PrixDevNouv)"
|
||||
)
|
||||
remise_nouvelle: float = Field(0.0, description="Nouvelle remise (AF_RemiseNouv)")
|
||||
date_application: Optional[datetime] = Field(
|
||||
None, description="Date application (AF_DateApplication)"
|
||||
)
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"fournisseur_num": "F001",
|
||||
"ref_fournisseur": "REF-FOURN-001",
|
||||
"prix_achat": 85.00,
|
||||
"delai_appro": 15,
|
||||
"est_principal": True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ReferenceEnumereeModel(BaseModel):
|
||||
"""Référence énumérée (article avec gammes)"""
|
||||
|
||||
gamme_1: int = Field(0, description="Gamme 1 (AG_No1)")
|
||||
gamme_2: int = Field(0, description="Gamme 2 (AG_No2)")
|
||||
reference_enumeree: str = Field(..., description="Référence énumérée (AE_Ref)")
|
||||
|
||||
prix_achat: float = Field(0.0, description="Prix achat (AE_PrixAch)")
|
||||
code_barre: Optional[str] = Field(None, description="Code-barres (AE_CodeBarre)")
|
||||
prix_achat_nouveau: float = Field(
|
||||
0.0, description="Nouveau prix achat (AE_PrixAchNouv)"
|
||||
)
|
||||
edi_code: Optional[str] = Field(None, description="Code EDI (AE_EdiCode)")
|
||||
en_sommeil: bool = Field(False, description="En sommeil (AE_Sommeil)")
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"gamme_1": 1,
|
||||
"gamme_2": 3,
|
||||
"reference_enumeree": "ART001-T1-C3",
|
||||
"prix_achat": 85.00,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MediaArticleModel(BaseModel):
|
||||
"""Média attaché à un article (photo, document, etc.)"""
|
||||
|
||||
commentaire: Optional[str] = Field(None, description="Commentaire (ME_Commentaire)")
|
||||
fichier: Optional[str] = Field(None, description="Nom fichier (ME_Fichier)")
|
||||
type_mime: Optional[str] = Field(None, description="Type MIME (ME_TypeMIME)")
|
||||
origine: int = Field(0, description="Origine (ME_Origine)")
|
||||
ged_id: Optional[str] = Field(None, description="ID GED (ME_GedId)")
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"commentaire": "Photo produit principale",
|
||||
"fichier": "ART001_photo1.jpg",
|
||||
"type_mime": "image/jpeg",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PrixGammeModel(BaseModel):
|
||||
"""Prix spécifique par combinaison de gammes"""
|
||||
|
||||
gamme_1: int = Field(0, description="Gamme 1 (AG_No1)")
|
||||
gamme_2: int = Field(0, description="Gamme 2 (AG_No2)")
|
||||
prix_net: float = Field(0.0, description="Prix net (AR_PUNet)")
|
||||
cout_standard: float = Field(0.0, description="Coût standard (AR_CoutStd)")
|
||||
|
||||
date_creation: Optional[datetime] = Field(None, description="Date création")
|
||||
date_modification: Optional[datetime] = Field(None, description="Date modification")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"gamme_1": 1,
|
||||
"gamme_2": 3,
|
||||
"prix_net": 125.50,
|
||||
"cout_standard": 82.30,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ArticleResponse(BaseModel):
|
||||
"""Article complet avec tous les enrichissements disponibles"""
|
||||
|
||||
reference: str = Field(..., description="Référence article (AR_Ref)")
|
||||
designation: str = Field(..., description="Désignation principale (AR_Design)")
|
||||
|
||||
code_ean: Optional[str] = Field(
|
||||
None, description="Code EAN / Code-barres principal (AR_CodeBarre)"
|
||||
)
|
||||
code_barre: Optional[str] = Field(
|
||||
None, description="Code-barres (alias de code_ean)"
|
||||
)
|
||||
edi_code: Optional[str] = Field(None, description="Code EDI (AR_EdiCode)")
|
||||
raccourci: Optional[str] = Field(None, description="Code raccourci (AR_Raccourci)")
|
||||
|
||||
prix_vente: float = Field(..., description="Prix de vente HT unitaire (AR_PrixVen)")
|
||||
prix_achat: Optional[float] = Field(
|
||||
None, description="Prix d'achat HT (AR_PrixAch)"
|
||||
)
|
||||
coef: Optional[float] = Field(
|
||||
None, description="Coefficient multiplicateur (AR_Coef)"
|
||||
)
|
||||
prix_net: Optional[float] = Field(None, description="Prix unitaire net (AR_PUNet)")
|
||||
|
||||
prix_achat_nouveau: Optional[float] = Field(
|
||||
None, description="Nouveau prix d'achat à venir (AR_PrixAchNouv)"
|
||||
)
|
||||
coef_nouveau: Optional[float] = Field(
|
||||
None, description="Nouveau coefficient à venir (AR_CoefNouv)"
|
||||
)
|
||||
prix_vente_nouveau: Optional[float] = Field(
|
||||
None, description="Nouveau prix de vente à venir (AR_PrixVenNouv)"
|
||||
)
|
||||
date_application_prix: Optional[str] = Field(
|
||||
None, description="Date d'application des nouveaux prix (AR_DateApplication)"
|
||||
)
|
||||
|
||||
cout_standard: Optional[float] = Field(
|
||||
None, description="Coût standard (AR_CoutStd)"
|
||||
)
|
||||
|
||||
stock_reel: float = Field(
|
||||
default=0.0, description="Stock réel total (F_ARTSTOCK.AS_QteSto)"
|
||||
)
|
||||
stock_mini: Optional[float] = Field(
|
||||
None, description="Stock minimum (F_ARTSTOCK.AS_QteMini)"
|
||||
)
|
||||
stock_maxi: Optional[float] = Field(
|
||||
None, description="Stock maximum (F_ARTSTOCK.AS_QteMaxi)"
|
||||
)
|
||||
stock_reserve: Optional[float] = Field(
|
||||
None, description="Stock réservé / en commande client (F_ARTSTOCK.AS_QteRes)"
|
||||
)
|
||||
stock_commande: Optional[float] = Field(
|
||||
None, description="Stock en commande fournisseur (F_ARTSTOCK.AS_QteCom)"
|
||||
)
|
||||
stock_disponible: Optional[float] = Field(
|
||||
None, description="Stock disponible = réel - réservé"
|
||||
)
|
||||
|
||||
emplacements: List[EmplacementStockModel] = Field(
|
||||
default_factory=list,
|
||||
description="Détail du stock par emplacement (F_ARTSTOCKEMPL + F_DEPOT + F_DEPOTEMPL)",
|
||||
)
|
||||
nb_emplacements: int = Field(0, description="Nombre d'emplacements")
|
||||
|
||||
suivi_stock: Optional[bool] = Field(
|
||||
None, description="Suivi de stock activé (AR_SuiviStock)"
|
||||
)
|
||||
nomenclature: Optional[bool] = Field(
|
||||
None, description="Article avec nomenclature (AR_Nomencl)"
|
||||
)
|
||||
qte_composant: Optional[float] = Field(
|
||||
None, description="Quantité de composant (AR_QteComp)"
|
||||
)
|
||||
qte_operatoire: Optional[float] = Field(
|
||||
None, description="Quantité opératoire (AR_QteOperatoire)"
|
||||
)
|
||||
|
||||
unite_vente: Optional[str] = Field(
|
||||
None, max_length=10, description="Unité de vente (AR_UniteVen)"
|
||||
)
|
||||
unite_poids: Optional[str] = Field(
|
||||
None, max_length=10, description="Unité de poids (AR_UnitePoids)"
|
||||
)
|
||||
poids_net: Optional[float] = Field(
|
||||
None, description="Poids net unitaire en kg (AR_PoidsNet)"
|
||||
)
|
||||
poids_brut: Optional[float] = Field(
|
||||
None, description="Poids brut unitaire en kg (AR_PoidsBrut)"
|
||||
)
|
||||
|
||||
gamme_1: Optional[str] = Field(None, description="Énumération gamme 1 (AR_Gamme1)")
|
||||
gamme_2: Optional[str] = Field(None, description="Énumération gamme 2 (AR_Gamme2)")
|
||||
|
||||
gammes: List[GammeArticleModel] = Field(
|
||||
default_factory=list,
|
||||
description="Détail des gammes (F_ARTGAMME + F_ENUMGAMME + P_GAMME)",
|
||||
)
|
||||
nb_gammes: int = Field(0, description="Nombre de gammes")
|
||||
|
||||
tarifs_clients: List[TarifClientModel] = Field(
|
||||
default_factory=list,
|
||||
description="Tarifs spécifiques par client/catégorie (F_ARTCLIENT)",
|
||||
)
|
||||
nb_tarifs_clients: int = Field(0, description="Nombre de tarifs clients")
|
||||
|
||||
composants: List[ComposantModel] = Field(
|
||||
default_factory=list,
|
||||
description="Composants/Opérations de production (F_ARTCOMPO)",
|
||||
)
|
||||
nb_composants: int = Field(0, description="Nombre de composants")
|
||||
|
||||
compta_vente: List[ComptaArticleModel] = Field(
|
||||
default_factory=list, description="Comptabilité vente (F_ARTCOMPTA type 0)"
|
||||
)
|
||||
compta_achat: List[ComptaArticleModel] = Field(
|
||||
default_factory=list, description="Comptabilité achat (F_ARTCOMPTA type 1)"
|
||||
)
|
||||
compta_stock: List[ComptaArticleModel] = Field(
|
||||
default_factory=list, description="Comptabilité stock (F_ARTCOMPTA type 2)"
|
||||
)
|
||||
|
||||
fournisseurs: List[FournisseurArticleModel] = Field(
|
||||
default_factory=list,
|
||||
description="Tous les fournisseurs de l'article (F_ARTFOURNISS)",
|
||||
)
|
||||
nb_fournisseurs: int = Field(0, description="Nombre de fournisseurs")
|
||||
|
||||
refs_enumerees: List[ReferenceEnumereeModel] = Field(
|
||||
default_factory=list, description="Références énumérées (F_ARTENUMREF)"
|
||||
)
|
||||
nb_refs_enumerees: int = Field(0, description="Nombre de références énumérées")
|
||||
|
||||
medias: List[MediaArticleModel] = Field(
|
||||
default_factory=list, description="Médias attachés (F_ARTICLEMEDIA)"
|
||||
)
|
||||
nb_medias: int = Field(0, description="Nombre de médias")
|
||||
|
||||
prix_gammes: List[PrixGammeModel] = Field(
|
||||
default_factory=list, description="Prix par combinaison de gammes (F_ARTPRIX)"
|
||||
)
|
||||
nb_prix_gammes: int = Field(0, description="Nombre de prix par gammes")
|
||||
|
||||
type_article: Optional[int] = Field(
|
||||
None,
|
||||
ge=0,
|
||||
le=3,
|
||||
description="Type : 0=Article, 1=Prestation, 2=Divers, 3=Nomenclature (AR_Type)",
|
||||
)
|
||||
type_article_libelle: Optional[str] = Field(
|
||||
None, description="Libellé du type d'article"
|
||||
)
|
||||
|
||||
famille_code: Optional[str] = Field(
|
||||
None, max_length=20, description="Code famille (FA_CodeFamille)"
|
||||
)
|
||||
famille_libelle: Optional[str] = Field(
|
||||
None, description="Libellé de la famille (F_FAMILLE.FA_Intitule)"
|
||||
)
|
||||
famille_type: Optional[int] = Field(
|
||||
None, description="Type de famille : 0=Détail, 1=Total (F_FAMILLE.FA_Type)"
|
||||
)
|
||||
famille_unite_vente: Optional[str] = Field(
|
||||
None, description="Unité de vente de la famille (F_FAMILLE.FA_UniteVen)"
|
||||
)
|
||||
famille_coef: Optional[float] = Field(
|
||||
None, description="Coefficient de la famille (F_FAMILLE.FA_Coef)"
|
||||
)
|
||||
famille_suivi_stock: Optional[bool] = Field(
|
||||
None, description="Suivi stock de la famille (F_FAMILLE.FA_SuiviStock)"
|
||||
)
|
||||
famille_garantie: Optional[int] = Field(
|
||||
None, description="Garantie de la famille (F_FAMILLE.FA_Garantie)"
|
||||
)
|
||||
famille_unite_poids: Optional[str] = Field(
|
||||
None, description="Unité de poids de la famille (F_FAMILLE.FA_UnitePoids)"
|
||||
)
|
||||
famille_delai: Optional[int] = Field(
|
||||
None, description="Délai de la famille (F_FAMILLE.FA_Delai)"
|
||||
)
|
||||
famille_nb_colis: Optional[int] = Field(
|
||||
None, description="Nombre de colis de la famille (F_FAMILLE.FA_NbColis)"
|
||||
)
|
||||
famille_code_fiscal: Optional[str] = Field(
|
||||
None, description="Code fiscal de la famille (F_FAMILLE.FA_CodeFiscal)"
|
||||
)
|
||||
famille_escompte: Optional[bool] = Field(
|
||||
None, description="Escompte de la famille (F_FAMILLE.FA_Escompte)"
|
||||
)
|
||||
famille_centrale: Optional[bool] = Field(
|
||||
None, description="Famille centrale (F_FAMILLE.FA_Central)"
|
||||
)
|
||||
famille_nature: Optional[int] = Field(
|
||||
None, description="Nature de la famille (F_FAMILLE.FA_Nature)"
|
||||
)
|
||||
famille_hors_stat: Optional[bool] = Field(
|
||||
None, description="Hors statistique famille (F_FAMILLE.FA_HorsStat)"
|
||||
)
|
||||
famille_pays: Optional[str] = Field(
|
||||
None, description="Pays de la famille (F_FAMILLE.FA_Pays)"
|
||||
)
|
||||
|
||||
nature: Optional[int] = Field(None, description="Nature de l'article (AR_Nature)")
|
||||
garantie: Optional[int] = Field(
|
||||
None, description="Durée de garantie en mois (AR_Garantie)"
|
||||
)
|
||||
code_fiscal: Optional[str] = Field(
|
||||
None, max_length=10, description="Code fiscal/TVA (AR_CodeFiscal)"
|
||||
)
|
||||
pays: Optional[str] = Field(None, description="Pays d'origine (AR_Pays)")
|
||||
|
||||
fournisseur_principal: Optional[int] = Field(
|
||||
None,
|
||||
description="N° compte du fournisseur principal (CO_No → F_COMPTET.CT_Num)",
|
||||
)
|
||||
fournisseur_nom: Optional[str] = Field(
|
||||
None, description="Nom du fournisseur principal (F_COMPTET.CT_Intitule)"
|
||||
)
|
||||
|
||||
conditionnement: Optional[str] = Field(
|
||||
None, description="Conditionnement d'achat (AR_Condition)"
|
||||
)
|
||||
conditionnement_qte: Optional[float] = Field(
|
||||
None, description="Quantité conditionnement (F_ENUMCOND.EC_Quantite)"
|
||||
)
|
||||
conditionnement_edi: Optional[str] = Field(
|
||||
None, description="Code EDI conditionnement (F_ENUMCOND.EC_EdiCode)"
|
||||
)
|
||||
|
||||
nb_colis: Optional[int] = Field(
|
||||
None, description="Nombre de colis par unité (AR_NbColis)"
|
||||
)
|
||||
prevision: Optional[bool] = Field(
|
||||
None, description="Gestion en prévision (AR_Prevision)"
|
||||
)
|
||||
|
||||
est_actif: bool = Field(default=True, description="Article actif (AR_Sommeil = 0)")
|
||||
en_sommeil: bool = Field(
|
||||
default=False, description="Article en sommeil (AR_Sommeil = 1)"
|
||||
)
|
||||
article_substitut: Optional[str] = Field(
|
||||
None, description="Référence article de substitution (AR_Substitut)"
|
||||
)
|
||||
soumis_escompte: Optional[bool] = Field(
|
||||
None, description="Soumis à escompte (AR_Escompte)"
|
||||
)
|
||||
delai: Optional[int] = Field(
|
||||
None, description="Délai de livraison en jours (AR_Delai)"
|
||||
)
|
||||
|
||||
publie: Optional[bool] = Field(
|
||||
None, description="Publié sur web/catalogue (AR_Publie)"
|
||||
)
|
||||
hors_statistique: Optional[bool] = Field(
|
||||
None, description="Exclus des statistiques (AR_HorsStat)"
|
||||
)
|
||||
vente_debit: Optional[bool] = Field(
|
||||
None, description="Vente au débit (AR_VteDebit)"
|
||||
)
|
||||
non_imprimable: Optional[bool] = Field(
|
||||
None, description="Non imprimable sur documents (AR_NotImp)"
|
||||
)
|
||||
transfere: Optional[bool] = Field(
|
||||
None, description="Article transféré (AR_Transfere)"
|
||||
)
|
||||
contremarque: Optional[bool] = Field(
|
||||
None, description="Article en contremarque (AR_Contremarque)"
|
||||
)
|
||||
fact_poids: Optional[bool] = Field(
|
||||
None, description="Facturation au poids (AR_FactPoids)"
|
||||
)
|
||||
fact_forfait: Optional[bool] = Field(
|
||||
None, description="Facturation au forfait (AR_FactForfait)"
|
||||
)
|
||||
saisie_variable: Optional[bool] = Field(
|
||||
None, description="Saisie variable (AR_SaisieVar)"
|
||||
)
|
||||
fictif: Optional[bool] = Field(None, description="Article fictif (AR_Fictif)")
|
||||
sous_traitance: Optional[bool] = Field(
|
||||
None, description="Article en sous-traitance (AR_SousTraitance)"
|
||||
)
|
||||
criticite: Optional[int] = Field(
|
||||
None, description="Niveau de criticité (AR_Criticite)"
|
||||
)
|
||||
|
||||
reprise_code_defaut: Optional[str] = Field(
|
||||
None, description="Code reprise par défaut (RP_CodeDefaut)"
|
||||
)
|
||||
delai_fabrication: Optional[int] = Field(
|
||||
None, description="Délai de fabrication (AR_DelaiFabrication)"
|
||||
)
|
||||
delai_peremption: Optional[int] = Field(
|
||||
None, description="Délai de péremption (AR_DelaiPeremption)"
|
||||
)
|
||||
delai_securite: Optional[int] = Field(
|
||||
None, description="Délai de sécurité (AR_DelaiSecurite)"
|
||||
)
|
||||
type_lancement: Optional[int] = Field(
|
||||
None, description="Type de lancement production (AR_TypeLancement)"
|
||||
)
|
||||
cycle: Optional[int] = Field(None, description="Cycle de production (AR_Cycle)")
|
||||
|
||||
photo: Optional[str] = Field(
|
||||
None, description="Chemin/nom du fichier photo (AR_Photo)"
|
||||
)
|
||||
langue_1: Optional[str] = Field(None, description="Texte en langue 1 (AR_Langue1)")
|
||||
langue_2: Optional[str] = Field(None, description="Texte en langue 2 (AR_Langue2)")
|
||||
|
||||
frais_01_denomination: Optional[str] = Field(
|
||||
None, description="Dénomination frais 1 (AR_Frais01FR_Denomination)"
|
||||
)
|
||||
frais_02_denomination: Optional[str] = Field(
|
||||
None, description="Dénomination frais 2 (AR_Frais02FR_Denomination)"
|
||||
)
|
||||
frais_03_denomination: Optional[str] = Field(
|
||||
None, description="Dénomination frais 3 (AR_Frais03FR_Denomination)"
|
||||
)
|
||||
|
||||
tva_code: Optional[str] = Field(None, description="Code TVA (F_TAXE.TA_Code)")
|
||||
tva_taux: Optional[float] = Field(
|
||||
None, description="Taux de TVA en % (F_TAXE.TA_Taux)"
|
||||
)
|
||||
|
||||
stat_01: Optional[str] = Field(None, description="Statistique 1 (AR_Stat01)")
|
||||
stat_02: Optional[str] = Field(None, description="Statistique 2 (AR_Stat02)")
|
||||
stat_03: Optional[str] = Field(None, description="Statistique 3 (AR_Stat03)")
|
||||
stat_04: Optional[str] = Field(None, description="Statistique 4 (AR_Stat04)")
|
||||
stat_05: Optional[str] = Field(None, description="Statistique 5 (AR_Stat05)")
|
||||
|
||||
categorie_1: Optional[int] = Field(
|
||||
None, description="Catégorie comptable 1 (CL_No1)"
|
||||
)
|
||||
categorie_2: Optional[int] = Field(
|
||||
None, description="Catégorie comptable 2 (CL_No2)"
|
||||
)
|
||||
categorie_3: Optional[int] = Field(
|
||||
None, description="Catégorie comptable 3 (CL_No3)"
|
||||
)
|
||||
categorie_4: Optional[int] = Field(
|
||||
None, description="Catégorie comptable 4 (CL_No4)"
|
||||
)
|
||||
|
||||
date_modification: Optional[str] = Field(
|
||||
None, description="Date de dernière modification (AR_DateModif)"
|
||||
)
|
||||
|
||||
marque_commerciale: Optional[str] = Field(
|
||||
None, description="Marque commerciale (champ personnalisé)"
|
||||
)
|
||||
objectif_qtes_vendues: Optional[str] = Field(
|
||||
None, description="Objectif / Quantités vendues (champ personnalisé)"
|
||||
)
|
||||
pourcentage_or: Optional[str] = Field(
|
||||
None, description="Pourcentage teneur en or (champ personnalisé)"
|
||||
)
|
||||
premiere_commercialisation: Optional[str] = Field(
|
||||
None, description="Date de 1ère commercialisation (champ personnalisé)"
|
||||
)
|
||||
interdire_commande: Optional[bool] = Field(
|
||||
None, description="Interdire la commande (champ personnalisé)"
|
||||
)
|
||||
exclure: Optional[bool] = Field(
|
||||
None, description="Exclure de certains traitements (champ personnalisé)"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"reference": "BAGUE-001",
|
||||
"designation": "Bague Or 18K Diamant",
|
||||
"prix_vente": 1299.00,
|
||||
"stock_reel": 15.0,
|
||||
"stock_disponible": 13.0,
|
||||
"nb_emplacements": 2,
|
||||
"nb_gammes": 2,
|
||||
"nb_tarifs_clients": 3,
|
||||
"nb_fournisseurs": 2,
|
||||
"nb_medias": 2,
|
||||
"emplacements": [
|
||||
{
|
||||
"depot": "01",
|
||||
"emplacement": "A1-01",
|
||||
"qte_stockee": 10.0,
|
||||
"depot_nom": "Dépôt principal",
|
||||
}
|
||||
],
|
||||
"gammes": [
|
||||
{"numero_gamme": 1, "enumere": "001", "gamme_nom": "Taille"}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ArticleListResponse(BaseModel):
|
||||
"""Réponse pour une liste d'articles"""
|
||||
|
||||
total: int = Field(..., description="Nombre total d'articles")
|
||||
articles: List[ArticleResponse] = Field(..., description="Liste des articles")
|
||||
filtre_applique: Optional[str] = Field(
|
||||
None, description="Filtre de recherche appliqué"
|
||||
)
|
||||
avec_stock: bool = Field(True, description="Indique si les stocks ont été chargés")
|
||||
avec_famille: bool = Field(
|
||||
True, description="Indique si les familles ont été enrichies"
|
||||
)
|
||||
avec_enrichissements_complets: bool = Field(
|
||||
False, description="Indique si tous les enrichissements sont activés"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"total": 1250,
|
||||
"filtre_applique": "bague",
|
||||
"avec_stock": True,
|
||||
"avec_famille": True,
|
||||
"avec_enrichissements_complets": True,
|
||||
"articles": [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ArticleCreateRequest(BaseModel):
|
||||
"""Schéma pour création d'article"""
|
||||
|
||||
reference: str = Field(..., max_length=18, description="Référence article")
|
||||
designation: str = Field(..., max_length=69, description="Désignation")
|
||||
famille: Optional[str] = Field(None, max_length=18, description="Code famille")
|
||||
prix_vente: Optional[float] = Field(None, ge=0, description="Prix vente HT")
|
||||
prix_achat: Optional[float] = Field(None, ge=0, description="Prix achat HT")
|
||||
stock_reel: Optional[float] = Field(None, ge=0, description="Stock initial")
|
||||
stock_mini: Optional[float] = Field(None, ge=0, description="Stock minimum")
|
||||
code_ean: Optional[str] = Field(None, max_length=13, description="Code-barres")
|
||||
unite_vente: Optional[str] = Field("UN", max_length=4, description="Unité")
|
||||
tva_code: Optional[str] = Field(None, max_length=5, description="Code TVA")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
|
||||
|
||||
class ArticleUpdateRequest(BaseModel):
|
||||
"""Schéma pour modification d'article"""
|
||||
|
||||
designation: Optional[str] = Field(None, max_length=69)
|
||||
prix_vente: Optional[float] = Field(None, ge=0)
|
||||
prix_achat: Optional[float] = Field(None, ge=0)
|
||||
stock_reel: Optional[float] = Field(
|
||||
None, ge=0, description="Critique pour erreur 2881"
|
||||
)
|
||||
stock_mini: Optional[float] = Field(None, ge=0)
|
||||
code_ean: Optional[str] = Field(None, max_length=13)
|
||||
description: Optional[str] = Field(None)
|
||||
|
||||
|
||||
class MouvementStockLigneRequest(BaseModel):
|
||||
article_ref: str = Field(..., description="Référence de l'article")
|
||||
quantite: float = Field(..., gt=0, description="Quantité (>0)")
|
||||
depot_code: Optional[str] = Field(None, description="Code du dépôt (ex: '01')")
|
||||
prix_unitaire: Optional[float] = Field(
|
||||
None, ge=0, description="Prix unitaire (optionnel)"
|
||||
)
|
||||
commentaire: Optional[str] = Field(None, description="Commentaire ligne")
|
||||
numero_lot: Optional[str] = Field(
|
||||
None, description="Numéro de lot (pour FIFO/LIFO)"
|
||||
)
|
||||
stock_mini: Optional[float] = Field(
|
||||
None,
|
||||
ge=0,
|
||||
description="""Stock minimum à définir pour cet article.
|
||||
Si fourni, met à jour AS_QteMini dans F_ARTSTOCK.
|
||||
Laisser None pour ne pas modifier.""",
|
||||
)
|
||||
stock_maxi: Optional[float] = Field(
|
||||
None,
|
||||
ge=0,
|
||||
description="""Stock maximum à définir pour cet article.
|
||||
Doit être > stock_mini si les deux sont fournis.""",
|
||||
)
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"article_ref": "ARTS-001",
|
||||
"quantite": 50.0,
|
||||
"depot_code": "01",
|
||||
"prix_unitaire": 100.0,
|
||||
"commentaire": "Réapprovisionnement",
|
||||
"numero_lot": "LOT20241217",
|
||||
"stock_mini": 10.0,
|
||||
"stock_maxi": 200.0,
|
||||
}
|
||||
}
|
||||
|
||||
@validator("stock_maxi")
|
||||
def validate_stock_maxi(cls, v, values):
|
||||
"""Valide que stock_maxi > stock_mini si les deux sont fournis"""
|
||||
if (
|
||||
v is not None
|
||||
and "stock_mini" in values
|
||||
and values["stock_mini"] is not None
|
||||
):
|
||||
if v <= values["stock_mini"]:
|
||||
raise ValueError(
|
||||
"stock_maxi doit être strictement supérieur à stock_mini"
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class EntreeStockRequest(BaseModel):
|
||||
"""Création d'un bon d'entrée en stock"""
|
||||
|
||||
date_entree: Optional[date] = Field(
|
||||
None, description="Date du mouvement (aujourd'hui par défaut)"
|
||||
)
|
||||
reference: Optional[str] = Field(None, description="Référence externe")
|
||||
depot_code: Optional[str] = Field(
|
||||
None, description="Dépôt principal (si applicable)"
|
||||
)
|
||||
lignes: List[MouvementStockLigneRequest] = Field(
|
||||
..., min_items=1, description="Lignes du mouvement"
|
||||
)
|
||||
commentaire: Optional[str] = Field(None, description="Commentaire général")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"date_entree": "2025-01-15",
|
||||
"reference": "REC-2025-001",
|
||||
"depot_code": "01",
|
||||
"lignes": [
|
||||
{
|
||||
"article_ref": "ART001",
|
||||
"quantite": 50,
|
||||
"depot_code": "01",
|
||||
"prix_unitaire": 10.50,
|
||||
"commentaire": "Réception fournisseur",
|
||||
}
|
||||
],
|
||||
"commentaire": "Réception livraison fournisseur XYZ",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SortieStockRequest(BaseModel):
|
||||
"""Création d'un bon de sortie de stock"""
|
||||
|
||||
date_sortie: Optional[date] = Field(
|
||||
None, description="Date du mouvement (aujourd'hui par défaut)"
|
||||
)
|
||||
reference: Optional[str] = Field(None, description="Référence externe")
|
||||
depot_code: Optional[str] = Field(
|
||||
None, description="Dépôt principal (si applicable)"
|
||||
)
|
||||
lignes: List[MouvementStockLigneRequest] = Field(
|
||||
..., min_items=1, description="Lignes du mouvement"
|
||||
)
|
||||
commentaire: Optional[str] = Field(None, description="Commentaire général")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"date_sortie": "2025-01-15",
|
||||
"reference": "SOR-2025-001",
|
||||
"depot_code": "01",
|
||||
"lignes": [
|
||||
{
|
||||
"article_ref": "ART001",
|
||||
"quantite": 10,
|
||||
"depot_code": "01",
|
||||
"commentaire": "Utilisation interne",
|
||||
}
|
||||
],
|
||||
"commentaire": "Consommation atelier",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MouvementStockResponse(BaseModel):
|
||||
"""Réponse pour un mouvement de stock"""
|
||||
|
||||
article_ref: str = Field(..., description="Numéro d'article")
|
||||
numero: str = Field(..., description="Numéro du mouvement")
|
||||
type: int = Field(..., description="Type (0=Entrée, 1=Sortie)")
|
||||
type_libelle: str = Field(..., description="Libellé du type")
|
||||
date: str = Field(..., description="Date du mouvement")
|
||||
reference: Optional[str] = Field(None, description="Référence externe")
|
||||
nb_lignes: int = Field(..., description="Nombre de lignes")
|
||||
213
schemas/articles/famille_article.py
Normal file
213
schemas/articles/famille_article.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
class FamilleCreateRequest(BaseModel):
|
||||
"""Schéma pour création de famille d'articles"""
|
||||
|
||||
code: str = Field(..., max_length=18, description="Code famille (max 18 car)")
|
||||
intitule: str = Field(..., max_length=69, description="Intitulé (max 69 car)")
|
||||
type: int = Field(0, ge=0, le=1, description="0=Détail, 1=Total")
|
||||
compte_achat: Optional[str] = Field(
|
||||
None, max_length=13, description="Compte général achat (ex: 607000)"
|
||||
)
|
||||
compte_vente: Optional[str] = Field(
|
||||
None, max_length=13, description="Compte général vente (ex: 707000)"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"code": "PRODLAIT",
|
||||
"intitule": "Produits laitiers",
|
||||
"type": 0,
|
||||
"compte_achat": "607000",
|
||||
"compte_vente": "707000",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class FamilleResponse(BaseModel):
|
||||
"""Modèle complet d'une famille avec données comptables et fournisseur"""
|
||||
|
||||
code: str = Field(..., description="Code famille")
|
||||
intitule: str = Field(..., description="Intitulé")
|
||||
type: int = Field(..., description="Type (0=Détail, 1=Total)")
|
||||
type_libelle: str = Field(..., description="Libellé du type")
|
||||
est_total: bool = Field(..., description="True si type Total")
|
||||
est_detail: bool = Field(..., description="True si type Détail")
|
||||
|
||||
unite_vente: Optional[str] = Field(None, description="Unité de vente par défaut")
|
||||
unite_poids: Optional[str] = Field(None, description="Unité de poids")
|
||||
coef: Optional[float] = Field(None, description="Coefficient multiplicateur")
|
||||
|
||||
suivi_stock: Optional[bool] = Field(None, description="Suivi du stock activé")
|
||||
garantie: Optional[int] = Field(None, description="Durée de garantie (mois)")
|
||||
delai: Optional[int] = Field(None, description="Délai de livraison (jours)")
|
||||
nb_colis: Optional[int] = Field(None, description="Nombre de colis")
|
||||
|
||||
code_fiscal: Optional[str] = Field(None, description="Code fiscal par défaut")
|
||||
escompte: Optional[bool] = Field(None, description="Escompte autorisé")
|
||||
|
||||
est_centrale: Optional[bool] = Field(None, description="Famille d'achat centralisé")
|
||||
nature: Optional[int] = Field(None, description="Nature de la famille")
|
||||
pays: Optional[str] = Field(None, description="Pays d'origine")
|
||||
|
||||
categorie_1: Optional[int] = Field(None, description="Catégorie comptable 1 (CL_No1)")
|
||||
categorie_2: Optional[int] = Field(None, description="Catégorie comptable 2 (CL_No2)")
|
||||
categorie_3: Optional[int] = Field(None, description="Catégorie comptable 3 (CL_No3)")
|
||||
categorie_4: Optional[int] = Field(None, description="Catégorie comptable 4 (CL_No4)")
|
||||
|
||||
stat_01: Optional[str] = Field(None, description="Statistique libre 1")
|
||||
stat_02: Optional[str] = Field(None, description="Statistique libre 2")
|
||||
stat_03: Optional[str] = Field(None, description="Statistique libre 3")
|
||||
stat_04: Optional[str] = Field(None, description="Statistique libre 4")
|
||||
stat_05: Optional[str] = Field(None, description="Statistique libre 5")
|
||||
hors_statistique: Optional[bool] = Field(None, description="Exclue des statistiques")
|
||||
|
||||
vente_debit: Optional[bool] = Field(None, description="Vente au débit")
|
||||
non_imprimable: Optional[bool] = Field(None, description="Non imprimable sur documents")
|
||||
contremarque: Optional[bool] = Field(None, description="Article en contremarque")
|
||||
fact_poids: Optional[bool] = Field(None, description="Facturation au poids")
|
||||
fact_forfait: Optional[bool] = Field(None, description="Facturation forfaitaire")
|
||||
publie: Optional[bool] = Field(None, description="Publié (e-commerce)")
|
||||
|
||||
racine_reference: Optional[str] = Field(None, description="Racine pour génération auto de références")
|
||||
racine_code_barre: Optional[str] = Field(None, description="Racine pour génération auto de codes-barres")
|
||||
raccourci: Optional[str] = Field(None, description="Raccourci clavier")
|
||||
|
||||
sous_traitance: Optional[bool] = Field(None, description="Famille en sous-traitance")
|
||||
fictif: Optional[bool] = Field(None, description="Famille fictive (nomenclature)")
|
||||
criticite: Optional[int] = Field(None, description="Niveau de criticité (0-5)")
|
||||
|
||||
compte_vente: Optional[str] = Field(None, description="Compte général de vente")
|
||||
compte_auxiliaire_vente: Optional[str] = Field(None, description="Compte auxiliaire de vente")
|
||||
tva_vente_1: Optional[str] = Field(None, description="Code TVA vente principal")
|
||||
tva_vente_2: Optional[str] = Field(None, description="Code TVA vente secondaire")
|
||||
tva_vente_3: Optional[str] = Field(None, description="Code TVA vente tertiaire")
|
||||
type_facture_vente: Optional[int] = Field(None, description="Type de facture vente")
|
||||
|
||||
compte_achat: Optional[str] = Field(None, description="Compte général d'achat")
|
||||
compte_auxiliaire_achat: Optional[str] = Field(None, description="Compte auxiliaire d'achat")
|
||||
tva_achat_1: Optional[str] = Field(None, description="Code TVA achat principal")
|
||||
tva_achat_2: Optional[str] = Field(None, description="Code TVA achat secondaire")
|
||||
tva_achat_3: Optional[str] = Field(None, description="Code TVA achat tertiaire")
|
||||
type_facture_achat: Optional[int] = Field(None, description="Type de facture achat")
|
||||
|
||||
compte_stock: Optional[str] = Field(None, description="Compte de stock")
|
||||
compte_auxiliaire_stock: Optional[str] = Field(None, description="Compte auxiliaire de stock")
|
||||
|
||||
fournisseur_principal: Optional[str] = Field(None, description="N° compte fournisseur principal")
|
||||
fournisseur_unite: Optional[str] = Field(None, description="Unité d'achat fournisseur")
|
||||
fournisseur_conversion: Optional[float] = Field(None, description="Coefficient de conversion")
|
||||
fournisseur_delai_appro: Optional[int] = Field(None, description="Délai d'approvisionnement (jours)")
|
||||
fournisseur_garantie: Optional[int] = Field(None, description="Garantie fournisseur (mois)")
|
||||
fournisseur_colisage: Optional[int] = Field(None, description="Colisage fournisseur")
|
||||
fournisseur_qte_mini: Optional[float] = Field(None, description="Quantité minimum de commande")
|
||||
fournisseur_qte_mont: Optional[float] = Field(None, description="Quantité montant")
|
||||
fournisseur_devise: Optional[int] = Field(None, description="Devise fournisseur (0=Euro)")
|
||||
fournisseur_remise: Optional[float] = Field(None, description="Remise fournisseur (%)")
|
||||
fournisseur_type_remise: Optional[int] = Field(None, description="Type de remise (0=%, 1=Montant)")
|
||||
|
||||
nb_articles: Optional[int] = Field(None, description="Nombre d'articles dans la famille")
|
||||
|
||||
FA_CodeFamille: Optional[str] = Field(None, description="[Legacy] Code famille")
|
||||
FA_Intitule: Optional[str] = Field(None, description="[Legacy] Intitulé")
|
||||
FA_Type: Optional[int] = Field(None, description="[Legacy] Type")
|
||||
CG_NumVte: Optional[str] = Field(None, description="[Legacy] Compte vente")
|
||||
CG_NumAch: Optional[str] = Field(None, description="[Legacy] Compte achat")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"code": "ELECT",
|
||||
"intitule": "Électronique et Informatique",
|
||||
"type": 0,
|
||||
"type_libelle": "Détail",
|
||||
"est_total": False,
|
||||
"est_detail": True,
|
||||
"unite_vente": "U",
|
||||
"unite_poids": "KG",
|
||||
"coef": 2.5,
|
||||
"suivi_stock": True,
|
||||
"garantie": 24,
|
||||
"delai": 5,
|
||||
"nb_colis": 1,
|
||||
"code_fiscal": "C19",
|
||||
"escompte": True,
|
||||
"est_centrale": False,
|
||||
"nature": 0,
|
||||
"pays": "FR",
|
||||
"categorie_1": 1,
|
||||
"categorie_2": 0,
|
||||
"categorie_3": 0,
|
||||
"categorie_4": 0,
|
||||
"stat_01": "HIGH_TECH",
|
||||
"stat_02": "",
|
||||
"stat_03": "",
|
||||
"stat_04": "",
|
||||
"stat_05": "",
|
||||
"hors_statistique": False,
|
||||
"vente_debit": False,
|
||||
"non_imprimable": False,
|
||||
"contremarque": False,
|
||||
"fact_poids": False,
|
||||
"fact_forfait": False,
|
||||
"publie": True,
|
||||
"racine_reference": "ELEC",
|
||||
"racine_code_barre": "339",
|
||||
"raccourci": "F5",
|
||||
"sous_traitance": False,
|
||||
"fictif": False,
|
||||
"criticite": 2,
|
||||
"compte_vente": "707100",
|
||||
"compte_auxiliaire_vente": "",
|
||||
"tva_vente_1": "C19",
|
||||
"tva_vente_2": "",
|
||||
"tva_vente_3": "",
|
||||
"type_facture_vente": 0,
|
||||
"compte_achat": "607100",
|
||||
"compte_auxiliaire_achat": "",
|
||||
"tva_achat_1": "C19",
|
||||
"tva_achat_2": "",
|
||||
"tva_achat_3": "",
|
||||
"type_facture_achat": 0,
|
||||
"compte_stock": "350000",
|
||||
"compte_auxiliaire_stock": "",
|
||||
"fournisseur_principal": "FTECH001",
|
||||
"fournisseur_unite": "U",
|
||||
"fournisseur_conversion": 1.0,
|
||||
"fournisseur_delai_appro": 7,
|
||||
"fournisseur_garantie": 12,
|
||||
"fournisseur_colisage": 10,
|
||||
"fournisseur_qte_mini": 5.0,
|
||||
"fournisseur_qte_mont": 100.0,
|
||||
"fournisseur_devise": 0,
|
||||
"fournisseur_remise": 5.0,
|
||||
"fournisseur_type_remise": 0,
|
||||
"nb_articles": 156
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FamilleListResponse(BaseModel):
|
||||
"""Réponse pour la liste des familles"""
|
||||
familles: list[FamilleResponse]
|
||||
total: int
|
||||
filtre: Optional[str] = None
|
||||
inclure_totaux: bool = True
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"familles": [],
|
||||
"total": 42,
|
||||
"filtre": "ELECT",
|
||||
"inclure_totaux": False
|
||||
}
|
||||
}
|
||||
|
||||
71
schemas/documents/avoirs.py
Normal file
71
schemas/documents/avoirs.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
class LigneAvoir(BaseModel):
|
||||
"""Ligne d'avoir"""
|
||||
|
||||
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 AvoirCreateRequest(BaseModel):
|
||||
"""Création d'un avoir"""
|
||||
|
||||
client_id: str
|
||||
date_avoir: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
lignes: List[LigneAvoir]
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"client_id": "CLI000001",
|
||||
"date_avoir": "2024-01-15",
|
||||
"reference": "AV-EXT-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 5.0,
|
||||
"prix_unitaire_ht": 50.0,
|
||||
"remise_pourcentage": 0.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AvoirUpdateRequest(BaseModel):
|
||||
"""Modification d'un avoir existant"""
|
||||
|
||||
date_avoir: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
lignes: Optional[List[LigneAvoir]] = None
|
||||
statut: Optional[int] = Field(None, ge=0, le=6)
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"date_avoir": "2024-01-15",
|
||||
"date_livraison": "2024-01-15",
|
||||
"reference": "AV-EXT-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 10.0,
|
||||
"prix_unitaire_ht": 45.0,
|
||||
}
|
||||
],
|
||||
"statut": 2,
|
||||
}
|
||||
}
|
||||
71
schemas/documents/commandes.py
Normal file
71
schemas/documents/commandes.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
class LigneCommande(BaseModel):
|
||||
"""Ligne de commande"""
|
||||
|
||||
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 CommandeCreateRequest(BaseModel):
|
||||
"""Création d'une commande"""
|
||||
|
||||
client_id: str
|
||||
date_commande: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
lignes: List[LigneCommande]
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"client_id": "CLI000001",
|
||||
"date_commande": "2024-01-15",
|
||||
"reference": "CMD-EXT-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 10.0,
|
||||
"prix_unitaire_ht": 50.0,
|
||||
"remise_pourcentage": 5.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CommandeUpdateRequest(BaseModel):
|
||||
"""Modification d'une commande existante"""
|
||||
|
||||
date_commande: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
lignes: Optional[List[LigneCommande]] = None
|
||||
statut: Optional[int] = Field(None, ge=0, le=6)
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"date_commande": "2024-01-15",
|
||||
"date_livraison": "2024-01-15",
|
||||
"reference": "CMD-EXT-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 15.0,
|
||||
"prix_unitaire_ht": 45.0,
|
||||
}
|
||||
],
|
||||
"statut": 2,
|
||||
}
|
||||
}
|
||||
66
schemas/documents/devis.py
Normal file
66
schemas/documents/devis.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
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):
|
||||
client_id: str
|
||||
date_devis: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
reference: Optional[str] = None
|
||||
lignes: List[LigneDevis]
|
||||
|
||||
|
||||
class DevisResponse(BaseModel):
|
||||
id: str
|
||||
client_id: str
|
||||
date_devis: str
|
||||
montant_total_ht: float
|
||||
montant_total_ttc: float
|
||||
nb_lignes: int
|
||||
|
||||
|
||||
|
||||
class DevisUpdateRequest(BaseModel):
|
||||
"""Modèle pour modification d'un devis existant"""
|
||||
|
||||
date_devis: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
reference: Optional[str] = None
|
||||
lignes: Optional[List[LigneDevis]] = None
|
||||
statut: Optional[int] = Field(None, ge=0, le=6)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"date_devis": "2024-01-15",
|
||||
"date_livraison": "2024-01-15",
|
||||
"reference": "DEV-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 5.0,
|
||||
"prix_unitaire_ht": 100.0,
|
||||
"remise_pourcentage": 10.0,
|
||||
}
|
||||
],
|
||||
"statut": 2,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class RelanceDevisRequest(BaseModel):
|
||||
doc_id: str
|
||||
message_personnalise: Optional[str] = None
|
||||
22
schemas/documents/documents.py
Normal file
22
schemas/documents/documents.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
from config import settings
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
class TypeDocument(int, Enum):
|
||||
DEVIS = settings.SAGE_TYPE_DEVIS
|
||||
BON_COMMANDE = settings.SAGE_TYPE_BON_COMMANDE
|
||||
PREPARATION = settings.SAGE_TYPE_PREPARATION
|
||||
BON_LIVRAISON = settings.SAGE_TYPE_BON_LIVRAISON
|
||||
BON_RETOUR = settings.SAGE_TYPE_BON_RETOUR
|
||||
BON_AVOIR = settings.SAGE_TYPE_BON_AVOIR
|
||||
FACTURE = settings.SAGE_TYPE_FACTURE
|
||||
|
||||
|
||||
class TypeDocumentSQL(int, Enum):
|
||||
DEVIS = settings.SAGE_TYPE_DEVIS
|
||||
BON_COMMANDE = 1
|
||||
PREPARATION = 2
|
||||
BON_LIVRAISON = 3
|
||||
BON_RETOUR = 4
|
||||
BON_AVOIR = 5
|
||||
FACTURE = 6
|
||||
22
schemas/documents/email.py
Normal file
22
schemas/documents/email.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
from schemas.documents.documents import TypeDocument
|
||||
|
||||
class StatutEmail(str, Enum):
|
||||
EN_ATTENTE = "EN_ATTENTE"
|
||||
EN_COURS = "EN_COURS"
|
||||
ENVOYE = "ENVOYE"
|
||||
OUVERT = "OUVERT"
|
||||
ERREUR = "ERREUR"
|
||||
BOUNCE = "BOUNCE"
|
||||
|
||||
class EmailEnvoiRequest(BaseModel):
|
||||
destinataire: EmailStr
|
||||
cc: Optional[List[EmailStr]] = []
|
||||
cci: Optional[List[EmailStr]] = []
|
||||
sujet: str
|
||||
corps_html: str
|
||||
document_ids: Optional[List[str]] = None
|
||||
type_document: Optional[TypeDocument] = None
|
||||
70
schemas/documents/factures.py
Normal file
70
schemas/documents/factures.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
class LigneFacture(BaseModel):
|
||||
"""Ligne de facture"""
|
||||
|
||||
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 FactureCreateRequest(BaseModel):
|
||||
"""Création d'une facture"""
|
||||
|
||||
client_id: str
|
||||
date_facture: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
lignes: List[LigneFacture]
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"client_id": "CLI000001",
|
||||
"date_facture": "2024-01-15",
|
||||
"reference": "FA-EXT-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 10.0,
|
||||
"prix_unitaire_ht": 50.0,
|
||||
"remise_pourcentage": 5.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FactureUpdateRequest(BaseModel):
|
||||
"""Modification d'une facture existante"""
|
||||
|
||||
date_facture: Optional[date] = None
|
||||
date_livraison: Optional[date] = None
|
||||
lignes: Optional[List[LigneFacture]] = None
|
||||
statut: Optional[int] = Field(None, ge=0, le=6)
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"date_facture": "2024-01-15",
|
||||
"date_livraison": "2024-01-15",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 15.0,
|
||||
"prix_unitaire_ht": 45.0,
|
||||
}
|
||||
],
|
||||
"statut": 2,
|
||||
}
|
||||
}
|
||||
70
schemas/documents/livraisons.py
Normal file
70
schemas/documents/livraisons.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
class LigneLivraison(BaseModel):
|
||||
"""Ligne de livraison"""
|
||||
|
||||
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 LivraisonCreateRequest(BaseModel):
|
||||
"""Création d'une livraison"""
|
||||
|
||||
client_id: str
|
||||
date_livraison: Optional[date] = None
|
||||
date_livraison_prevue: Optional[date] = None
|
||||
lignes: List[LigneLivraison]
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"client_id": "CLI000001",
|
||||
"date_livraison": "2024-01-15",
|
||||
"reference": "BL-EXT-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 10.0,
|
||||
"prix_unitaire_ht": 50.0,
|
||||
"remise_pourcentage": 5.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LivraisonUpdateRequest(BaseModel):
|
||||
"""Modification d'une livraison existante"""
|
||||
|
||||
date_livraison: Optional[date] = None
|
||||
date_livraison_prevue: Optional[date] = None
|
||||
lignes: Optional[List[LigneLivraison]] = None
|
||||
statut: Optional[int] = Field(None, ge=0, le=6)
|
||||
reference: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"date_livraison": "2024-01-15",
|
||||
"date_livraison_prevue": "2024-01-15",
|
||||
"reference": "BL-EXT-001",
|
||||
"lignes": [
|
||||
{
|
||||
"article_code": "ART001",
|
||||
"quantite": 15.0,
|
||||
"prix_unitaire_ht": 45.0,
|
||||
}
|
||||
],
|
||||
"statut": 2,
|
||||
}
|
||||
}
|
||||
19
schemas/documents/universign.py
Normal file
19
schemas/documents/universign.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
from pydantic import BaseModel, Field, EmailStr, validator, field_validator
|
||||
from typing import List, Optional, Dict, ClassVar, Any
|
||||
from datetime import date, datetime
|
||||
from enum import Enum, IntEnum
|
||||
from schemas.documents.documents import TypeDocument
|
||||
|
||||
class StatutSignature(str, Enum):
|
||||
EN_ATTENTE = "EN_ATTENTE"
|
||||
ENVOYE = "ENVOYE"
|
||||
SIGNE = "SIGNE"
|
||||
REFUSE = "REFUSE"
|
||||
EXPIRE = "EXPIRE"
|
||||
|
||||
class SignatureRequest(BaseModel):
|
||||
doc_id: str
|
||||
type_doc: TypeDocument
|
||||
email_signataire: EmailStr
|
||||
nom_signataire: str
|
||||
9
schemas/schema_mixte.py
Normal file
9
schemas/schema_mixte.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BaremeRemiseResponse(BaseModel):
|
||||
client_id: str
|
||||
remise_max_autorisee: float
|
||||
remise_demandee: float
|
||||
autorisee: bool
|
||||
message: str
|
||||
829
schemas/tiers/clients.py
Normal file
829
schemas/tiers/clients.py
Normal file
|
|
@ -0,0 +1,829 @@
|
|||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import List, Optional
|
||||
from schemas.tiers.contact import Contact
|
||||
|
||||
class ClientResponse(BaseModel):
|
||||
"""Modèle de réponse client simplifié (pour listes)"""
|
||||
|
||||
numero: Optional[str] = None
|
||||
intitule: Optional[str] = None
|
||||
adresse: Optional[str] = None
|
||||
code_postal: Optional[str] = None
|
||||
ville: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
telephone: Optional[str] = None
|
||||
|
||||
|
||||
class ClientDetails(BaseModel):
|
||||
numero: Optional[str] = Field(None, description="Code client (CT_Num)")
|
||||
intitule: Optional[str] = Field(
|
||||
None, description="Raison sociale ou Nom complet (CT_Intitule)"
|
||||
)
|
||||
type_tiers: Optional[int] = Field(
|
||||
None, description="Type : 0=Client, 1=Fournisseur (CT_Type)"
|
||||
)
|
||||
qualite: Optional[str] = Field(
|
||||
None, description="Qualité Sage : CLI, FOU, PRO (CT_Qualite)"
|
||||
)
|
||||
classement: Optional[str] = Field(
|
||||
None, description="Code de classement (CT_Classement)"
|
||||
)
|
||||
raccourci: Optional[str] = Field(
|
||||
None, description="Code raccourci 7 car. (CT_Raccourci)"
|
||||
)
|
||||
siret: Optional[str] = Field(None, description="N° SIRET 14 chiffres (CT_Siret)")
|
||||
tva_intra: Optional[str] = Field(
|
||||
None, description="N° TVA intracommunautaire (CT_Identifiant)"
|
||||
)
|
||||
code_naf: Optional[str] = Field(None, description="Code NAF/APE (CT_Ape)")
|
||||
|
||||
contact: Optional[str] = Field(
|
||||
None, description="Nom du contact principal (CT_Contact)"
|
||||
)
|
||||
adresse: Optional[str] = Field(None, description="Adresse ligne 1 (CT_Adresse)")
|
||||
complement: Optional[str] = Field(
|
||||
None, description="Complément d'adresse (CT_Complement)"
|
||||
)
|
||||
code_postal: Optional[str] = Field(None, description="Code postal (CT_CodePostal)")
|
||||
ville: Optional[str] = Field(None, description="Ville (CT_Ville)")
|
||||
region: Optional[str] = Field(None, description="Région/État (CT_CodeRegion)")
|
||||
pays: Optional[str] = Field(None, description="Pays (CT_Pays)")
|
||||
|
||||
telephone: Optional[str] = Field(None, description="Téléphone fixe (CT_Telephone)")
|
||||
telecopie: Optional[str] = Field(None, description="Fax (CT_Telecopie)")
|
||||
email: Optional[str] = Field(None, description="Email principal (CT_EMail)")
|
||||
site_web: Optional[str] = Field(None, description="Site web (CT_Site)")
|
||||
facebook: Optional[str] = Field(None, description="Profil Facebook (CT_Facebook)")
|
||||
linkedin: Optional[str] = Field(None, description="Profil LinkedIn (CT_LinkedIn)")
|
||||
|
||||
taux01: Optional[float] = Field(None, description="Taux personnalisé 1 (CT_Taux01)")
|
||||
taux02: Optional[float] = Field(None, description="Taux personnalisé 2 (CT_Taux02)")
|
||||
taux03: Optional[float] = Field(None, description="Taux personnalisé 3 (CT_Taux03)")
|
||||
taux04: Optional[float] = Field(None, description="Taux personnalisé 4 (CT_Taux04)")
|
||||
|
||||
statistique01: Optional[str] = Field(
|
||||
None, description="Statistique 1 (CT_Statistique01)"
|
||||
)
|
||||
statistique02: Optional[str] = Field(
|
||||
None, description="Statistique 2 (CT_Statistique02)"
|
||||
)
|
||||
statistique03: Optional[str] = Field(
|
||||
None, description="Statistique 3 (CT_Statistique03)"
|
||||
)
|
||||
statistique04: Optional[str] = Field(
|
||||
None, description="Statistique 4 (CT_Statistique04)"
|
||||
)
|
||||
statistique05: Optional[str] = Field(
|
||||
None, description="Statistique 5 (CT_Statistique05)"
|
||||
)
|
||||
statistique06: Optional[str] = Field(
|
||||
None, description="Statistique 6 (CT_Statistique06)"
|
||||
)
|
||||
statistique07: Optional[str] = Field(
|
||||
None, description="Statistique 7 (CT_Statistique07)"
|
||||
)
|
||||
statistique08: Optional[str] = Field(
|
||||
None, description="Statistique 8 (CT_Statistique08)"
|
||||
)
|
||||
statistique09: Optional[str] = Field(
|
||||
None, description="Statistique 9 (CT_Statistique09)"
|
||||
)
|
||||
statistique10: Optional[str] = Field(
|
||||
None, description="Statistique 10 (CT_Statistique10)"
|
||||
)
|
||||
|
||||
encours_autorise: Optional[float] = Field(
|
||||
None, description="Encours maximum autorisé (CT_Encours)"
|
||||
)
|
||||
assurance_credit: Optional[float] = Field(
|
||||
None, description="Montant assurance crédit (CT_Assurance)"
|
||||
)
|
||||
langue: Optional[int] = Field(
|
||||
None, description="Code langue 0=FR, 1=EN (CT_Langue)"
|
||||
)
|
||||
commercial_code: Optional[int] = Field(
|
||||
None, description="Code du commercial (CO_No)"
|
||||
)
|
||||
|
||||
lettrage_auto: Optional[bool] = Field(
|
||||
None, description="Lettrage automatique (CT_Lettrage)"
|
||||
)
|
||||
est_actif: Optional[bool] = Field(None, description="True si actif (CT_Sommeil=0)")
|
||||
type_facture: Optional[int] = Field(
|
||||
None, description="Type facture 0=Facture, 1=BL (CT_Facture)"
|
||||
)
|
||||
est_prospect: Optional[bool] = Field(
|
||||
None, description="True si prospect (CT_Prospect=1)"
|
||||
)
|
||||
bl_en_facture: Optional[int] = Field(
|
||||
None, description="Imprimer BL en facture (CT_BLFact)"
|
||||
)
|
||||
saut_page: Optional[int] = Field(
|
||||
None, description="Saut de page sur documents (CT_Saut)"
|
||||
)
|
||||
validation_echeance: Optional[int] = Field(
|
||||
None, description="Valider les échéances (CT_ValidEch)"
|
||||
)
|
||||
controle_encours: Optional[int] = Field(
|
||||
None, description="Contrôler l'encours (CT_ControlEnc)"
|
||||
)
|
||||
exclure_relance: Optional[bool] = Field(
|
||||
None, description="Exclure des relances (CT_NotRappel)"
|
||||
)
|
||||
exclure_penalites: Optional[bool] = Field(
|
||||
None, description="Exclure des pénalités (CT_NotPenal)"
|
||||
)
|
||||
bon_a_payer: Optional[int] = Field(
|
||||
None, description="Bon à payer obligatoire (CT_BonAPayer)"
|
||||
)
|
||||
|
||||
priorite_livraison: Optional[int] = Field(
|
||||
None, description="Priorité livraison (CT_PrioriteLivr)"
|
||||
)
|
||||
livraison_partielle: Optional[int] = Field(
|
||||
None, description="Livraison partielle (CT_LivrPartielle)"
|
||||
)
|
||||
delai_transport: Optional[int] = Field(
|
||||
None, description="Délai transport jours (CT_DelaiTransport)"
|
||||
)
|
||||
delai_appro: Optional[int] = Field(
|
||||
None, description="Délai appro jours (CT_DelaiAppro)"
|
||||
)
|
||||
|
||||
commentaire: Optional[str] = Field(
|
||||
None, description="Commentaire libre (CT_Commentaire)"
|
||||
)
|
||||
|
||||
section_analytique: Optional[str] = Field(
|
||||
None, description="Section analytique (CA_Num)"
|
||||
)
|
||||
|
||||
mode_reglement_code: Optional[int] = Field(
|
||||
None, description="Code mode règlement (MR_No)"
|
||||
)
|
||||
surveillance_active: Optional[bool] = Field(
|
||||
None, description="Surveillance financière (CT_Surveillance)"
|
||||
)
|
||||
coface: Optional[str] = Field(None, description="Code Coface 25 car. (CT_Coface)")
|
||||
forme_juridique: Optional[str] = Field(
|
||||
None, description="Forme juridique SA, SARL (CT_SvFormeJuri)"
|
||||
)
|
||||
effectif: Optional[str] = Field(
|
||||
None, description="Nombre d'employés (CT_SvEffectif)"
|
||||
)
|
||||
sv_regularite: Optional[str] = Field(
|
||||
None, description="Régularité paiements (CT_SvRegul)"
|
||||
)
|
||||
sv_cotation: Optional[str] = Field(
|
||||
None, description="Cotation crédit (CT_SvCotation)"
|
||||
)
|
||||
sv_objet_maj: Optional[str] = Field(
|
||||
None, description="Objet dernière MAJ (CT_SvObjetMaj)"
|
||||
)
|
||||
sv_chiffre_affaires: Optional[float] = Field(
|
||||
None, description="Chiffre d'affaires (CT_SvCA)"
|
||||
)
|
||||
sv_resultat: Optional[float] = Field(
|
||||
None, description="Résultat financier (CT_SvResultat)"
|
||||
)
|
||||
|
||||
compte_general: Optional[str] = Field(
|
||||
None, description="Compte général principal (CG_NumPrinc)"
|
||||
)
|
||||
categorie_tarif: Optional[int] = Field(
|
||||
None, description="Catégorie tarifaire (N_CatTarif)"
|
||||
)
|
||||
categorie_compta: Optional[int] = Field(
|
||||
None, description="Catégorie comptable (N_CatCompta)"
|
||||
)
|
||||
|
||||
contacts: Optional[List[Contact]] = Field(
|
||||
default_factory=list, description="Liste des contacts du client"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"numero": "CLI000001",
|
||||
"intitule": "SARL EXEMPLE",
|
||||
"type_tiers": 0,
|
||||
"qualite": "CLI",
|
||||
"classement": "A",
|
||||
"raccourci": "EXEMPL",
|
||||
"siret": "12345678901234",
|
||||
"tva_intra": "FR12345678901",
|
||||
"code_naf": "6201Z",
|
||||
"contact": "Jean Dupont",
|
||||
"adresse": "123 Rue de la Paix",
|
||||
"complement": "Bâtiment B",
|
||||
"code_postal": "75001",
|
||||
"ville": "Paris",
|
||||
"region": "Île-de-France",
|
||||
"pays": "France",
|
||||
"telephone": "0123456789",
|
||||
"telecopie": "0123456788",
|
||||
"email": "contact@exemple.fr",
|
||||
"site_web": "https://www.exemple.fr",
|
||||
"facebook": "https://facebook.com/exemple",
|
||||
"linkedin": "https://linkedin.com/company/exemple",
|
||||
"taux01": 0.0,
|
||||
"taux02": 0.0,
|
||||
"taux03": 0.0,
|
||||
"taux04": 0.0,
|
||||
"statistique01": "Informatique",
|
||||
"statistique02": "",
|
||||
"statistique03": "",
|
||||
"statistique04": "",
|
||||
"statistique05": "",
|
||||
"statistique06": "",
|
||||
"statistique07": "",
|
||||
"statistique08": "",
|
||||
"statistique09": "",
|
||||
"statistique10": "",
|
||||
"encours_autorise": 50000.0,
|
||||
"assurance_credit": 40000.0,
|
||||
"langue": 0,
|
||||
"commercial_code": 1,
|
||||
"lettrage_auto": True,
|
||||
"est_actif": True,
|
||||
"type_facture": 1,
|
||||
"est_prospect": False,
|
||||
"bl_en_facture": 0,
|
||||
"saut_page": 0,
|
||||
"validation_echeance": 0,
|
||||
"controle_encours": 1,
|
||||
"exclure_relance": False,
|
||||
"exclure_penalites": False,
|
||||
"bon_a_payer": 0,
|
||||
"priorite_livraison": 1,
|
||||
"livraison_partielle": 1,
|
||||
"delai_transport": 2,
|
||||
"delai_appro": 0,
|
||||
"commentaire": "Client important",
|
||||
"section_analytique": "",
|
||||
"mode_reglement_code": 1,
|
||||
"surveillance_active": True,
|
||||
"coface": "COF12345",
|
||||
"forme_juridique": "SARL",
|
||||
"effectif": "50-99",
|
||||
"sv_regularite": "",
|
||||
"sv_cotation": "",
|
||||
"sv_objet_maj": "",
|
||||
"sv_chiffre_affaires": 2500000.0,
|
||||
"sv_resultat": 150000.0,
|
||||
"compte_general": "4110000",
|
||||
"categorie_tarif": 0,
|
||||
"categorie_compta": 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ClientCreateRequest(BaseModel):
|
||||
intitule: str = Field(
|
||||
..., max_length=69, description="Nom du client (CT_Intitule) - OBLIGATOIRE"
|
||||
)
|
||||
|
||||
numero: str = Field(
|
||||
..., max_length=17, description="Numéro client CT_Num (auto si None)"
|
||||
)
|
||||
|
||||
type_tiers: int = Field(
|
||||
0,
|
||||
ge=0,
|
||||
le=3,
|
||||
description="CT_Type: 0=Client, 1=Fournisseur, 2=Salarié, 3=Autre",
|
||||
)
|
||||
|
||||
qualite: Optional[str] = Field(
|
||||
"CLI", max_length=17, description="CT_Qualite: CLI/FOU/SAL/DIV/AUT"
|
||||
)
|
||||
|
||||
classement: Optional[str] = Field(None, max_length=17, description="CT_Classement")
|
||||
|
||||
raccourci: Optional[str] = Field(
|
||||
None, max_length=7, description="CT_Raccourci (7 chars max, unique)"
|
||||
)
|
||||
|
||||
siret: Optional[str] = Field(
|
||||
None, max_length=15, description="CT_Siret (14-15 chars)"
|
||||
)
|
||||
|
||||
tva_intra: Optional[str] = Field(
|
||||
None, max_length=25, description="CT_Identifiant (TVA intracommunautaire)"
|
||||
)
|
||||
|
||||
code_naf: Optional[str] = Field(
|
||||
None, max_length=7, description="CT_Ape (Code NAF/APE)"
|
||||
)
|
||||
|
||||
contact: Optional[str] = Field(
|
||||
None,
|
||||
max_length=35,
|
||||
description="CT_Contact (double affectation: client + adresse)",
|
||||
)
|
||||
|
||||
adresse: Optional[str] = Field(None, max_length=35, description="Adresse.Adresse")
|
||||
|
||||
complement: Optional[str] = Field(
|
||||
None, max_length=35, description="Adresse.Complement"
|
||||
)
|
||||
|
||||
code_postal: Optional[str] = Field(
|
||||
None, max_length=9, description="Adresse.CodePostal"
|
||||
)
|
||||
|
||||
ville: Optional[str] = Field(None, max_length=35, description="Adresse.Ville")
|
||||
|
||||
region: Optional[str] = Field(None, max_length=25, description="Adresse.CodeRegion")
|
||||
|
||||
pays: Optional[str] = Field(None, max_length=35, description="Adresse.Pays")
|
||||
|
||||
telephone: Optional[str] = Field(
|
||||
None, max_length=21, description="Telecom.Telephone"
|
||||
)
|
||||
|
||||
telecopie: Optional[str] = Field(
|
||||
None, max_length=21, description="Telecom.Telecopie (fax)"
|
||||
)
|
||||
|
||||
email: Optional[str] = Field(None, max_length=69, description="Telecom.EMail")
|
||||
|
||||
site_web: Optional[str] = Field(None, max_length=69, description="Telecom.Site")
|
||||
|
||||
portable: Optional[str] = Field(None, max_length=21, description="Telecom.Portable")
|
||||
|
||||
facebook: Optional[str] = Field(
|
||||
None, max_length=69, description="Telecom.Facebook ou CT_Facebook"
|
||||
)
|
||||
|
||||
linkedin: Optional[str] = Field(
|
||||
None, max_length=69, description="Telecom.LinkedIn ou CT_LinkedIn"
|
||||
)
|
||||
|
||||
compte_general: Optional[str] = Field(
|
||||
None,
|
||||
max_length=13,
|
||||
description="CompteGPrinc (défaut selon type_tiers: 4110000, 4010000, 421, 471)",
|
||||
)
|
||||
|
||||
categorie_tarifaire: Optional[str] = Field(
|
||||
None, description="N_CatTarif (ID catégorie tarifaire, défaut '0' ou '1')"
|
||||
)
|
||||
|
||||
categorie_comptable: Optional[str] = Field(
|
||||
None, description="N_CatCompta (ID catégorie comptable, défaut '0' ou '1')"
|
||||
)
|
||||
|
||||
taux01: Optional[float] = Field(None, description="CT_Taux01")
|
||||
taux02: Optional[float] = Field(None, description="CT_Taux02")
|
||||
taux03: Optional[float] = Field(None, description="CT_Taux03")
|
||||
taux04: Optional[float] = Field(None, description="CT_Taux04")
|
||||
|
||||
secteur: Optional[str] = Field(
|
||||
None, max_length=21, description="Alias de statistique01 (CT_Statistique01)"
|
||||
)
|
||||
|
||||
statistique01: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique01"
|
||||
)
|
||||
statistique02: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique02"
|
||||
)
|
||||
statistique03: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique03"
|
||||
)
|
||||
statistique04: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique04"
|
||||
)
|
||||
statistique05: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique05"
|
||||
)
|
||||
statistique06: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique06"
|
||||
)
|
||||
statistique07: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique07"
|
||||
)
|
||||
statistique08: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique08"
|
||||
)
|
||||
statistique09: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique09"
|
||||
)
|
||||
statistique10: Optional[str] = Field(
|
||||
None, max_length=21, description="CT_Statistique10"
|
||||
)
|
||||
|
||||
encours_autorise: Optional[float] = Field(
|
||||
None, description="CT_Encours (montant max autorisé)"
|
||||
)
|
||||
|
||||
assurance_credit: Optional[float] = Field(
|
||||
None, description="CT_Assurance (montant assurance crédit)"
|
||||
)
|
||||
|
||||
langue: Optional[int] = Field(
|
||||
None, ge=0, description="CT_Langue (0=Français, 1=Anglais, etc.)"
|
||||
)
|
||||
|
||||
commercial_code: Optional[int] = Field(
|
||||
None, description="CO_No (ID du collaborateur commercial)"
|
||||
)
|
||||
|
||||
lettrage_auto: Optional[bool] = Field(
|
||||
True, description="CT_Lettrage (1=oui, 0=non)"
|
||||
)
|
||||
|
||||
est_actif: Optional[bool] = Field(
|
||||
True, description="Inverse de CT_Sommeil (True=actif, False=en sommeil)"
|
||||
)
|
||||
|
||||
type_facture: Optional[int] = Field(
|
||||
1, ge=0, le=2, description="CT_Facture: 0=aucune, 1=normale, 2=regroupée"
|
||||
)
|
||||
|
||||
est_prospect: Optional[bool] = Field(
|
||||
False, description="CT_Prospect (1=oui, 0=non)"
|
||||
)
|
||||
|
||||
bl_en_facture: Optional[int] = Field(
|
||||
None, ge=0, le=1, description="CT_BLFact (impression BL sur facture)"
|
||||
)
|
||||
|
||||
saut_page: Optional[int] = Field(
|
||||
None, ge=0, le=1, description="CT_Saut (saut de page après impression)"
|
||||
)
|
||||
|
||||
validation_echeance: Optional[int] = Field(
|
||||
None, ge=0, le=1, description="CT_ValidEch"
|
||||
)
|
||||
|
||||
controle_encours: Optional[int] = Field(
|
||||
None, ge=0, le=1, description="CT_ControlEnc"
|
||||
)
|
||||
|
||||
exclure_relance: Optional[int] = Field(None, ge=0, le=1, description="CT_NotRappel")
|
||||
|
||||
exclure_penalites: Optional[int] = Field(
|
||||
None, ge=0, le=1, description="CT_NotPenal"
|
||||
)
|
||||
|
||||
bon_a_payer: Optional[int] = Field(None, ge=0, le=1, description="CT_BonAPayer")
|
||||
|
||||
priorite_livraison: Optional[int] = Field(
|
||||
None, ge=0, le=5, description="CT_PrioriteLivr"
|
||||
)
|
||||
|
||||
livraison_partielle: Optional[int] = Field(
|
||||
None, ge=0, le=1, description="CT_LivrPartielle"
|
||||
)
|
||||
|
||||
delai_transport: Optional[int] = Field(
|
||||
None, ge=0, description="CT_DelaiTransport (jours)"
|
||||
)
|
||||
|
||||
delai_appro: Optional[int] = Field(None, ge=0, description="CT_DelaiAppro (jours)")
|
||||
|
||||
commentaire: Optional[str] = Field(
|
||||
None, max_length=35, description="CT_Commentaire"
|
||||
)
|
||||
|
||||
section_analytique: Optional[str] = Field(None, max_length=13, description="CA_Num")
|
||||
|
||||
mode_reglement_code: Optional[int] = Field(
|
||||
None, description="MR_No (ID du mode de règlement)"
|
||||
)
|
||||
|
||||
surveillance_active: Optional[int] = Field(
|
||||
None, ge=0, le=1, description="CT_Surveillance (DOIT être défini AVANT coface)"
|
||||
)
|
||||
|
||||
coface: Optional[str] = Field(
|
||||
None, max_length=25, description="CT_Coface (code Coface)"
|
||||
)
|
||||
|
||||
forme_juridique: Optional[str] = Field(
|
||||
None, max_length=33, description="CT_SvFormeJuri (SARL, SA, etc.)"
|
||||
)
|
||||
|
||||
effectif: Optional[str] = Field(None, max_length=11, description="CT_SvEffectif")
|
||||
|
||||
sv_regularite: Optional[str] = Field(None, max_length=3, description="CT_SvRegul")
|
||||
|
||||
sv_cotation: Optional[str] = Field(None, max_length=5, description="CT_SvCotation")
|
||||
|
||||
sv_objet_maj: Optional[str] = Field(
|
||||
None, max_length=61, description="CT_SvObjetMaj"
|
||||
)
|
||||
|
||||
ca_annuel: Optional[float] = Field(
|
||||
None,
|
||||
description="CT_SvCA (Chiffre d'affaires annuel) - alias: sv_chiffre_affaires",
|
||||
)
|
||||
|
||||
sv_chiffre_affaires: Optional[float] = Field(
|
||||
None, description="CT_SvCA (alias de ca_annuel)"
|
||||
)
|
||||
|
||||
sv_resultat: Optional[float] = Field(None, description="CT_SvResultat")
|
||||
|
||||
@field_validator("siret")
|
||||
@classmethod
|
||||
def validate_siret(cls, v):
|
||||
"""Valide et nettoie le SIRET"""
|
||||
if v and v.lower() not in ("none", "null", ""):
|
||||
cleaned = v.replace(" ", "").replace("-", "")
|
||||
if len(cleaned) not in (14, 15):
|
||||
raise ValueError("Le SIRET doit contenir 14 ou 15 caractères")
|
||||
return cleaned
|
||||
return None
|
||||
|
||||
@field_validator("email")
|
||||
@classmethod
|
||||
def validate_email(cls, v):
|
||||
"""Valide le format email"""
|
||||
if v and v.lower() not in ("none", "null", ""):
|
||||
v = v.strip()
|
||||
if "@" not in v:
|
||||
raise ValueError("Format email invalide")
|
||||
return v
|
||||
return None
|
||||
|
||||
@field_validator("raccourci")
|
||||
@classmethod
|
||||
def validate_raccourci(cls, v):
|
||||
"""Force le raccourci en majuscules"""
|
||||
if v and v.lower() not in ("none", "null", ""):
|
||||
return v.upper().strip()[:7]
|
||||
return None
|
||||
|
||||
@field_validator(
|
||||
"adresse",
|
||||
"code_postal",
|
||||
"ville",
|
||||
"pays",
|
||||
"telephone",
|
||||
"tva_intra",
|
||||
"contact",
|
||||
"complement",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
def clean_none_strings(cls, v):
|
||||
"""Convertit les chaînes 'None'/'null'/'' en None"""
|
||||
if isinstance(v, str) and v.lower() in ("none", "null", ""):
|
||||
return None
|
||||
return v
|
||||
|
||||
def to_sage_dict(self) -> dict:
|
||||
"""
|
||||
Convertit le modèle en dictionnaire compatible avec creer_client()
|
||||
Mapping 1:1 avec les paramètres réels de la fonction
|
||||
"""
|
||||
stat01 = self.statistique01 or self.secteur
|
||||
|
||||
ca = self.ca_annuel or self.sv_chiffre_affaires
|
||||
|
||||
return {
|
||||
"intitule": self.intitule,
|
||||
"numero": self.numero,
|
||||
"type_tiers": self.type_tiers,
|
||||
"qualite": self.qualite,
|
||||
"classement": self.classement,
|
||||
"raccourci": self.raccourci,
|
||||
"siret": self.siret,
|
||||
"tva_intra": self.tva_intra,
|
||||
"code_naf": self.code_naf,
|
||||
"contact": self.contact,
|
||||
"adresse": self.adresse,
|
||||
"complement": self.complement,
|
||||
"code_postal": self.code_postal,
|
||||
"ville": self.ville,
|
||||
"region": self.region,
|
||||
"pays": self.pays,
|
||||
"telephone": self.telephone,
|
||||
"telecopie": self.telecopie,
|
||||
"email": self.email,
|
||||
"site_web": self.site_web,
|
||||
"portable": self.portable,
|
||||
"facebook": self.facebook,
|
||||
"linkedin": self.linkedin,
|
||||
"compte_general": self.compte_general,
|
||||
"categorie_tarifaire": self.categorie_tarifaire,
|
||||
"categorie_comptable": self.categorie_comptable,
|
||||
"taux01": self.taux01,
|
||||
"taux02": self.taux02,
|
||||
"taux03": self.taux03,
|
||||
"taux04": self.taux04,
|
||||
"statistique01": stat01,
|
||||
"statistique02": self.statistique02,
|
||||
"statistique03": self.statistique03,
|
||||
"statistique04": self.statistique04,
|
||||
"statistique05": self.statistique05,
|
||||
"statistique06": self.statistique06,
|
||||
"statistique07": self.statistique07,
|
||||
"statistique08": self.statistique08,
|
||||
"statistique09": self.statistique09,
|
||||
"statistique10": self.statistique10,
|
||||
"secteur": self.secteur, # Gardé pour compatibilité
|
||||
"encours_autorise": self.encours_autorise,
|
||||
"assurance_credit": self.assurance_credit,
|
||||
"langue": self.langue,
|
||||
"commercial_code": self.commercial_code,
|
||||
"lettrage_auto": self.lettrage_auto,
|
||||
"est_actif": self.est_actif,
|
||||
"type_facture": self.type_facture,
|
||||
"est_prospect": self.est_prospect,
|
||||
"bl_en_facture": self.bl_en_facture,
|
||||
"saut_page": self.saut_page,
|
||||
"validation_echeance": self.validation_echeance,
|
||||
"controle_encours": self.controle_encours,
|
||||
"exclure_relance": self.exclure_relance,
|
||||
"exclure_penalites": self.exclure_penalites,
|
||||
"bon_a_payer": self.bon_a_payer,
|
||||
"priorite_livraison": self.priorite_livraison,
|
||||
"livraison_partielle": self.livraison_partielle,
|
||||
"delai_transport": self.delai_transport,
|
||||
"delai_appro": self.delai_appro,
|
||||
"commentaire": self.commentaire,
|
||||
"section_analytique": self.section_analytique,
|
||||
"mode_reglement_code": self.mode_reglement_code,
|
||||
"surveillance_active": self.surveillance_active,
|
||||
"coface": self.coface,
|
||||
"forme_juridique": self.forme_juridique,
|
||||
"effectif": self.effectif,
|
||||
"sv_regularite": self.sv_regularite,
|
||||
"sv_cotation": self.sv_cotation,
|
||||
"sv_objet_maj": self.sv_objet_maj,
|
||||
"ca_annuel": ca,
|
||||
"sv_chiffre_affaires": self.sv_chiffre_affaires,
|
||||
"sv_resultat": self.sv_resultat,
|
||||
}
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"intitule": "ENTREPRISE EXEMPLE SARL",
|
||||
"numero": "CLI00123",
|
||||
"type_tiers": 0,
|
||||
"qualite": "CLI",
|
||||
"compte_general": "411000",
|
||||
"est_prospect": False,
|
||||
"est_actif": True,
|
||||
"email": "contact@exemple.fr",
|
||||
"telephone": "0123456789",
|
||||
"adresse": "123 Rue de la Paix",
|
||||
"code_postal": "75001",
|
||||
"ville": "Paris",
|
||||
"pays": "France",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ClientUpdateRequest(BaseModel):
|
||||
"""
|
||||
Modèle pour modification d'un client existant
|
||||
TOUS les champs de ClientCreateRequest sont modifiables
|
||||
TOUS optionnels (seuls les champs fournis sont modifiés)
|
||||
"""
|
||||
|
||||
intitule: Optional[str] = Field(None, max_length=69)
|
||||
qualite: Optional[str] = Field(None, max_length=17)
|
||||
classement: Optional[str] = Field(None, max_length=17)
|
||||
raccourci: Optional[str] = Field(None, max_length=7)
|
||||
|
||||
siret: Optional[str] = Field(None, max_length=15)
|
||||
tva_intra: Optional[str] = Field(None, max_length=25)
|
||||
code_naf: Optional[str] = Field(None, max_length=7)
|
||||
|
||||
contact: Optional[str] = Field(None, max_length=35)
|
||||
adresse: Optional[str] = Field(None, max_length=35)
|
||||
complement: Optional[str] = Field(None, max_length=35)
|
||||
code_postal: Optional[str] = Field(None, max_length=9)
|
||||
ville: Optional[str] = Field(None, max_length=35)
|
||||
region: Optional[str] = Field(None, max_length=25)
|
||||
pays: Optional[str] = Field(None, max_length=35)
|
||||
|
||||
telephone: Optional[str] = Field(None, max_length=21)
|
||||
telecopie: Optional[str] = Field(None, max_length=21)
|
||||
email: Optional[str] = Field(None, max_length=69)
|
||||
site_web: Optional[str] = Field(None, max_length=69)
|
||||
portable: Optional[str] = Field(None, max_length=21)
|
||||
facebook: Optional[str] = Field(None, max_length=69)
|
||||
linkedin: Optional[str] = Field(None, max_length=69)
|
||||
|
||||
compte_general: Optional[str] = Field(None, max_length=13)
|
||||
|
||||
categorie_tarifaire: Optional[str] = None
|
||||
categorie_comptable: Optional[str] = None
|
||||
|
||||
taux01: Optional[float] = None
|
||||
taux02: Optional[float] = None
|
||||
taux03: Optional[float] = None
|
||||
taux04: Optional[float] = None
|
||||
|
||||
secteur: Optional[str] = Field(None, max_length=21)
|
||||
statistique01: Optional[str] = Field(None, max_length=21)
|
||||
statistique02: Optional[str] = Field(None, max_length=21)
|
||||
statistique03: Optional[str] = Field(None, max_length=21)
|
||||
statistique04: Optional[str] = Field(None, max_length=21)
|
||||
statistique05: Optional[str] = Field(None, max_length=21)
|
||||
statistique06: Optional[str] = Field(None, max_length=21)
|
||||
statistique07: Optional[str] = Field(None, max_length=21)
|
||||
statistique08: Optional[str] = Field(None, max_length=21)
|
||||
statistique09: Optional[str] = Field(None, max_length=21)
|
||||
statistique10: Optional[str] = Field(None, max_length=21)
|
||||
|
||||
encours_autorise: Optional[float] = None
|
||||
assurance_credit: Optional[float] = None
|
||||
langue: Optional[int] = Field(None, ge=0)
|
||||
commercial_code: Optional[int] = None
|
||||
|
||||
lettrage_auto: Optional[bool] = None
|
||||
est_actif: Optional[bool] = None
|
||||
type_facture: Optional[int] = Field(None, ge=0, le=2)
|
||||
est_prospect: Optional[bool] = None
|
||||
bl_en_facture: Optional[int] = Field(None, ge=0, le=1)
|
||||
saut_page: Optional[int] = Field(None, ge=0, le=1)
|
||||
validation_echeance: Optional[int] = Field(None, ge=0, le=1)
|
||||
controle_encours: Optional[int] = Field(None, ge=0, le=1)
|
||||
exclure_relance: Optional[int] = Field(None, ge=0, le=1)
|
||||
exclure_penalites: Optional[int] = Field(None, ge=0, le=1)
|
||||
bon_a_payer: Optional[int] = Field(None, ge=0, le=1)
|
||||
|
||||
priorite_livraison: Optional[int] = Field(None, ge=0, le=5)
|
||||
livraison_partielle: Optional[int] = Field(None, ge=0, le=1)
|
||||
delai_transport: Optional[int] = Field(None, ge=0)
|
||||
delai_appro: Optional[int] = Field(None, ge=0)
|
||||
|
||||
commentaire: Optional[str] = Field(None, max_length=35)
|
||||
|
||||
section_analytique: Optional[str] = Field(None, max_length=13)
|
||||
|
||||
mode_reglement_code: Optional[int] = None
|
||||
|
||||
surveillance_active: Optional[int] = Field(None, ge=0, le=1)
|
||||
coface: Optional[str] = Field(None, max_length=25)
|
||||
forme_juridique: Optional[str] = Field(None, max_length=33)
|
||||
effectif: Optional[str] = Field(None, max_length=11)
|
||||
sv_regularite: Optional[str] = Field(None, max_length=3)
|
||||
sv_cotation: Optional[str] = Field(None, max_length=5)
|
||||
sv_objet_maj: Optional[str] = Field(None, max_length=61)
|
||||
ca_annuel: Optional[float] = None
|
||||
sv_chiffre_affaires: Optional[float] = None
|
||||
sv_resultat: Optional[float] = None
|
||||
|
||||
@field_validator("siret")
|
||||
@classmethod
|
||||
def validate_siret(cls, v):
|
||||
if v and v.lower() not in ("none", "null", ""):
|
||||
cleaned = v.replace(" ", "").replace("-", "")
|
||||
if len(cleaned) not in (14, 15):
|
||||
raise ValueError("Le SIRET doit contenir 14 ou 15 caractères")
|
||||
return cleaned
|
||||
return None
|
||||
|
||||
@field_validator("email")
|
||||
@classmethod
|
||||
def validate_email(cls, v):
|
||||
if v and v.lower() not in ("none", "null", ""):
|
||||
v = v.strip()
|
||||
if "@" not in v:
|
||||
raise ValueError("Format email invalide")
|
||||
return v
|
||||
return None
|
||||
|
||||
@field_validator("raccourci")
|
||||
@classmethod
|
||||
def validate_raccourci(cls, v):
|
||||
if v and v.lower() not in ("none", "null", ""):
|
||||
return v.upper().strip()[:7]
|
||||
return None
|
||||
|
||||
@field_validator(
|
||||
"adresse",
|
||||
"code_postal",
|
||||
"ville",
|
||||
"pays",
|
||||
"telephone",
|
||||
"tva_intra",
|
||||
"contact",
|
||||
"complement",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
def clean_none_strings(cls, v):
|
||||
if isinstance(v, str) and v.lower() in ("none", "null", ""):
|
||||
return None
|
||||
return v
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"email": "nouveau@email.fr",
|
||||
"telephone": "0198765432",
|
||||
"portable": "0687654321",
|
||||
"adresse": "456 Avenue Nouvelle",
|
||||
"ville": "Lyon",
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,40 @@
|
|||
from typing import Optional, ClassVar
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import Optional, ClassVar
|
||||
|
||||
|
||||
class Contact(BaseModel):
|
||||
"""Contact associé à un tiers"""
|
||||
|
||||
numero: Optional[str] = Field(None, description="Code du tiers parent (CT_Num)")
|
||||
contact_numero: Optional[int] = Field(None, description="Numéro unique du contact (CT_No)")
|
||||
n_contact: Optional[int] = Field(None, description="Numéro de référence contact (N_Contact)")
|
||||
|
||||
civilite: Optional[str] = Field(None, description="Civilité : M., Mme, Mlle (CT_Civilite)")
|
||||
contact_numero: Optional[int] = Field(
|
||||
None, description="Numéro unique du contact (CT_No)"
|
||||
)
|
||||
n_contact: Optional[int] = Field(
|
||||
None, description="Numéro de référence contact (N_Contact)"
|
||||
)
|
||||
|
||||
civilite: Optional[str] = Field(
|
||||
None, description="Civilité : M., Mme, Mlle (CT_Civilite)"
|
||||
)
|
||||
nom: Optional[str] = Field(None, description="Nom de famille (CT_Nom)")
|
||||
prenom: Optional[str] = Field(None, description="Prénom (CT_Prenom)")
|
||||
fonction: Optional[str] = Field(None, description="Fonction/Titre (CT_Fonction)")
|
||||
|
||||
|
||||
service_code: Optional[int] = Field(None, description="Code du service (N_Service)")
|
||||
|
||||
|
||||
telephone: Optional[str] = Field(None, description="Téléphone fixe (CT_Telephone)")
|
||||
portable: Optional[str] = Field(None, description="Téléphone mobile (CT_TelPortable)")
|
||||
portable: Optional[str] = Field(
|
||||
None, description="Téléphone mobile (CT_TelPortable)"
|
||||
)
|
||||
telecopie: Optional[str] = Field(None, description="Fax (CT_Telecopie)")
|
||||
email: Optional[str] = Field(None, description="Adresse email (CT_EMail)")
|
||||
|
||||
|
||||
facebook: Optional[str] = Field(None, description="Profil Facebook (CT_Facebook)")
|
||||
linkedin: Optional[str] = Field(None, description="Profil LinkedIn (CT_LinkedIn)")
|
||||
skype: Optional[str] = Field(None, description="Identifiant Skype (CT_Skype)")
|
||||
|
||||
est_defaut: Optional[bool] = Field(False, description="Contact par défaut")
|
||||
|
||||
|
||||
civilite_map: ClassVar[dict] = {
|
||||
0: "M.",
|
||||
1: "Mme",
|
||||
|
|
@ -38,4 +48,70 @@ class Contact(BaseModel):
|
|||
return v
|
||||
if isinstance(v, int):
|
||||
return cls.civilite_map.get(v, str(v))
|
||||
return v
|
||||
return v
|
||||
|
||||
|
||||
class ContactCreate(BaseModel):
|
||||
"""Données pour créer ou modifier un contact"""
|
||||
|
||||
numero: str = Field(..., description="Code du client parent (obligatoire)")
|
||||
|
||||
civilite: Optional[str] = Field(None, description="M., Mme, Mlle, Société")
|
||||
nom: str = Field(..., description="Nom de famille (obligatoire)")
|
||||
prenom: Optional[str] = Field(None, description="Prénom")
|
||||
fonction: Optional[str] = Field(None, description="Fonction/Titre")
|
||||
|
||||
est_defaut: Optional[bool] = Field(
|
||||
False, description="Définir comme contact par défaut du client"
|
||||
)
|
||||
|
||||
service_code: Optional[int] = Field(None, description="Code du service")
|
||||
|
||||
telephone: Optional[str] = Field(None, description="Téléphone fixe")
|
||||
portable: Optional[str] = Field(None, description="Téléphone mobile")
|
||||
telecopie: Optional[str] = Field(None, description="Fax")
|
||||
email: Optional[str] = Field(None, description="Email")
|
||||
|
||||
facebook: Optional[str] = Field(None, description="URL Facebook")
|
||||
linkedin: Optional[str] = Field(None, description="URL LinkedIn")
|
||||
skype: Optional[str] = Field(None, description="Identifiant Skype")
|
||||
|
||||
@validator("civilite")
|
||||
def validate_civilite(cls, v):
|
||||
if v and v not in ["M.", "Mme", "Mlle", "Société"]:
|
||||
raise ValueError("Civilité doit être: M., Mme, Mlle ou Société")
|
||||
return v
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"numero": "CLI000001",
|
||||
"civilite": "M.",
|
||||
"nom": "Dupont",
|
||||
"prenom": "Jean",
|
||||
"fonction": "Directeur Commercial",
|
||||
"telephone": "0123456789",
|
||||
"portable": "0612345678",
|
||||
"email": "j.dupont@exemple.fr",
|
||||
"linkedin": "https://linkedin.com/in/jeandupont",
|
||||
"est_defaut": True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ContactUpdate(BaseModel):
|
||||
"""Données pour modifier un contact (tous champs optionnels)"""
|
||||
|
||||
civilite: Optional[str] = None
|
||||
nom: Optional[str] = None
|
||||
prenom: Optional[str] = None
|
||||
fonction: Optional[str] = None
|
||||
service_code: Optional[int] = None
|
||||
telephone: Optional[str] = None
|
||||
portable: Optional[str] = None
|
||||
telecopie: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
facebook: Optional[str] = None
|
||||
linkedin: Optional[str] = None
|
||||
skype: Optional[str] = None
|
||||
est_defaut: Optional[bool] = None
|
||||
|
|
|
|||
327
schemas/tiers/fournisseurs.py
Normal file
327
schemas/tiers/fournisseurs.py
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
from pydantic import BaseModel, Field, EmailStr
|
||||
from typing import List, Optional
|
||||
from schemas.tiers.contact import Contact
|
||||
|
||||
|
||||
class FournisseurDetails(BaseModel):
|
||||
numero: Optional[str] = Field(None, description="Code fournisseur (CT_Num)")
|
||||
intitule: Optional[str] = Field(
|
||||
None, description="Raison sociale ou Nom complet (CT_Intitule)"
|
||||
)
|
||||
type_tiers: Optional[int] = Field(
|
||||
None, description="Type : 0=Client, 1=Fournisseur (CT_Type)"
|
||||
)
|
||||
qualite: Optional[str] = Field(
|
||||
None, description="Qualité Sage : CLI, FOU, PRO (CT_Qualite)"
|
||||
)
|
||||
classement: Optional[str] = Field(
|
||||
None, description="Code de classement (CT_Classement)"
|
||||
)
|
||||
raccourci: Optional[str] = Field(
|
||||
None, description="Code raccourci 7 car. (CT_Raccourci)"
|
||||
)
|
||||
siret: Optional[str] = Field(None, description="N° SIRET 14 chiffres (CT_Siret)")
|
||||
tva_intra: Optional[str] = Field(
|
||||
None, description="N° TVA intracommunautaire (CT_Identifiant)"
|
||||
)
|
||||
code_naf: Optional[str] = Field(None, description="Code NAF/APE (CT_Ape)")
|
||||
|
||||
contact: Optional[str] = Field(
|
||||
None, description="Nom du contact principal (CT_Contact)"
|
||||
)
|
||||
adresse: Optional[str] = Field(None, description="Adresse ligne 1 (CT_Adresse)")
|
||||
complement: Optional[str] = Field(
|
||||
None, description="Complément d'adresse (CT_Complement)"
|
||||
)
|
||||
code_postal: Optional[str] = Field(None, description="Code postal (CT_CodePostal)")
|
||||
ville: Optional[str] = Field(None, description="Ville (CT_Ville)")
|
||||
region: Optional[str] = Field(None, description="Région/État (CT_CodeRegion)")
|
||||
pays: Optional[str] = Field(None, description="Pays (CT_Pays)")
|
||||
|
||||
telephone: Optional[str] = Field(None, description="Téléphone fixe (CT_Telephone)")
|
||||
telecopie: Optional[str] = Field(None, description="Fax (CT_Telecopie)")
|
||||
email: Optional[str] = Field(None, description="Email principal (CT_EMail)")
|
||||
site_web: Optional[str] = Field(None, description="Site web (CT_Site)")
|
||||
facebook: Optional[str] = Field(None, description="Profil Facebook (CT_Facebook)")
|
||||
linkedin: Optional[str] = Field(None, description="Profil LinkedIn (CT_LinkedIn)")
|
||||
|
||||
taux01: Optional[float] = Field(None, description="Taux personnalisé 1 (CT_Taux01)")
|
||||
taux02: Optional[float] = Field(None, description="Taux personnalisé 2 (CT_Taux02)")
|
||||
taux03: Optional[float] = Field(None, description="Taux personnalisé 3 (CT_Taux03)")
|
||||
taux04: Optional[float] = Field(None, description="Taux personnalisé 4 (CT_Taux04)")
|
||||
|
||||
statistique01: Optional[str] = Field(
|
||||
None, description="Statistique 1 (CT_Statistique01)"
|
||||
)
|
||||
statistique02: Optional[str] = Field(
|
||||
None, description="Statistique 2 (CT_Statistique02)"
|
||||
)
|
||||
statistique03: Optional[str] = Field(
|
||||
None, description="Statistique 3 (CT_Statistique03)"
|
||||
)
|
||||
statistique04: Optional[str] = Field(
|
||||
None, description="Statistique 4 (CT_Statistique04)"
|
||||
)
|
||||
statistique05: Optional[str] = Field(
|
||||
None, description="Statistique 5 (CT_Statistique05)"
|
||||
)
|
||||
statistique06: Optional[str] = Field(
|
||||
None, description="Statistique 6 (CT_Statistique06)"
|
||||
)
|
||||
statistique07: Optional[str] = Field(
|
||||
None, description="Statistique 7 (CT_Statistique07)"
|
||||
)
|
||||
statistique08: Optional[str] = Field(
|
||||
None, description="Statistique 8 (CT_Statistique08)"
|
||||
)
|
||||
statistique09: Optional[str] = Field(
|
||||
None, description="Statistique 9 (CT_Statistique09)"
|
||||
)
|
||||
statistique10: Optional[str] = Field(
|
||||
None, description="Statistique 10 (CT_Statistique10)"
|
||||
)
|
||||
|
||||
encours_autorise: Optional[float] = Field(
|
||||
None, description="Encours maximum autorisé (CT_Encours)"
|
||||
)
|
||||
assurance_credit: Optional[float] = Field(
|
||||
None, description="Montant assurance crédit (CT_Assurance)"
|
||||
)
|
||||
langue: Optional[int] = Field(
|
||||
None, description="Code langue 0=FR, 1=EN (CT_Langue)"
|
||||
)
|
||||
commercial_code: Optional[int] = Field(
|
||||
None, description="Code du commercial (CO_No)"
|
||||
)
|
||||
|
||||
lettrage_auto: Optional[bool] = Field(
|
||||
None, description="Lettrage automatique (CT_Lettrage)"
|
||||
)
|
||||
est_actif: Optional[bool] = Field(None, description="True si actif (CT_Sommeil=0)")
|
||||
type_facture: Optional[int] = Field(
|
||||
None, description="Type facture 0=Facture, 1=BL (CT_Facture)"
|
||||
)
|
||||
est_prospect: Optional[bool] = Field(
|
||||
None, description="True si prospect (CT_Prospect=1)"
|
||||
)
|
||||
bl_en_facture: Optional[int] = Field(
|
||||
None, description="Imprimer BL en facture (CT_BLFact)"
|
||||
)
|
||||
saut_page: Optional[int] = Field(
|
||||
None, description="Saut de page sur documents (CT_Saut)"
|
||||
)
|
||||
validation_echeance: Optional[int] = Field(
|
||||
None, description="Valider les échéances (CT_ValidEch)"
|
||||
)
|
||||
controle_encours: Optional[int] = Field(
|
||||
None, description="Contrôler l'encours (CT_ControlEnc)"
|
||||
)
|
||||
exclure_relance: Optional[bool] = Field(
|
||||
None, description="Exclure des relances (CT_NotRappel)"
|
||||
)
|
||||
exclure_penalites: Optional[bool] = Field(
|
||||
None, description="Exclure des pénalités (CT_NotPenal)"
|
||||
)
|
||||
bon_a_payer: Optional[int] = Field(
|
||||
None, description="Bon à payer obligatoire (CT_BonAPayer)"
|
||||
)
|
||||
|
||||
priorite_livraison: Optional[int] = Field(
|
||||
None, description="Priorité livraison (CT_PrioriteLivr)"
|
||||
)
|
||||
livraison_partielle: Optional[int] = Field(
|
||||
None, description="Livraison partielle (CT_LivrPartielle)"
|
||||
)
|
||||
delai_transport: Optional[int] = Field(
|
||||
None, description="Délai transport jours (CT_DelaiTransport)"
|
||||
)
|
||||
delai_appro: Optional[int] = Field(
|
||||
None, description="Délai appro jours (CT_DelaiAppro)"
|
||||
)
|
||||
|
||||
commentaire: Optional[str] = Field(
|
||||
None, description="Commentaire libre (CT_Commentaire)"
|
||||
)
|
||||
|
||||
section_analytique: Optional[str] = Field(
|
||||
None, description="Section analytique (CA_Num)"
|
||||
)
|
||||
|
||||
mode_reglement_code: Optional[int] = Field(
|
||||
None, description="Code mode règlement (MR_No)"
|
||||
)
|
||||
surveillance_active: Optional[bool] = Field(
|
||||
None, description="Surveillance financière (CT_Surveillance)"
|
||||
)
|
||||
coface: Optional[str] = Field(None, description="Code Coface 25 car. (CT_Coface)")
|
||||
forme_juridique: Optional[str] = Field(
|
||||
None, description="Forme juridique SA, SARL (CT_SvFormeJuri)"
|
||||
)
|
||||
effectif: Optional[str] = Field(
|
||||
None, description="Nombre d'employés (CT_SvEffectif)"
|
||||
)
|
||||
sv_regularite: Optional[str] = Field(
|
||||
None, description="Régularité paiements (CT_SvRegul)"
|
||||
)
|
||||
sv_cotation: Optional[str] = Field(
|
||||
None, description="Cotation crédit (CT_SvCotation)"
|
||||
)
|
||||
sv_objet_maj: Optional[str] = Field(
|
||||
None, description="Objet dernière MAJ (CT_SvObjetMaj)"
|
||||
)
|
||||
sv_chiffre_affaires: Optional[float] = Field(
|
||||
None, description="Chiffre d'affaires (CT_SvCA)"
|
||||
)
|
||||
sv_resultat: Optional[float] = Field(
|
||||
None, description="Résultat financier (CT_SvResultat)"
|
||||
)
|
||||
|
||||
compte_general: Optional[str] = Field(
|
||||
None, description="Compte général principal (CG_NumPrinc)"
|
||||
)
|
||||
categorie_tarif: Optional[int] = Field(
|
||||
None, description="Catégorie tarifaire (N_CatTarif)"
|
||||
)
|
||||
categorie_compta: Optional[int] = Field(
|
||||
None, description="Catégorie comptable (N_CatCompta)"
|
||||
)
|
||||
|
||||
contacts: Optional[List[Contact]] = Field(
|
||||
default_factory=list, description="Liste des contacts du fournisseur"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"numero": "FOU000001",
|
||||
"intitule": "SARL FOURNISSEUR EXEMPLE",
|
||||
"type_tiers": 1,
|
||||
"qualite": "FOU",
|
||||
"classement": "A",
|
||||
"raccourci": "EXEMPL",
|
||||
"siret": "12345678901234",
|
||||
"tva_intra": "FR12345678901",
|
||||
"code_naf": "6201Z",
|
||||
"contact": "Jean Dupont",
|
||||
"adresse": "123 Rue de la Paix",
|
||||
"complement": "Bâtiment B",
|
||||
"code_postal": "75001",
|
||||
"ville": "Paris",
|
||||
"region": "Île-de-France",
|
||||
"pays": "France",
|
||||
"telephone": "0123456789",
|
||||
"telecopie": "0123456788",
|
||||
"email": "contact@exemple.fr",
|
||||
"site_web": "https://www.exemple.fr",
|
||||
"facebook": "https://facebook.com/exemple",
|
||||
"linkedin": "https://linkedin.com/company/exemple",
|
||||
"taux01": 0.0,
|
||||
"taux02": 0.0,
|
||||
"taux03": 0.0,
|
||||
"taux04": 0.0,
|
||||
"statistique01": "Informatique",
|
||||
"statistique02": "",
|
||||
"statistique03": "",
|
||||
"statistique04": "",
|
||||
"statistique05": "",
|
||||
"statistique06": "",
|
||||
"statistique07": "",
|
||||
"statistique08": "",
|
||||
"statistique09": "",
|
||||
"statistique10": "",
|
||||
"encours_autorise": 50000.0,
|
||||
"assurance_credit": 40000.0,
|
||||
"langue": 0,
|
||||
"commercial_code": 1,
|
||||
"lettrage_auto": True,
|
||||
"est_actif": True,
|
||||
"type_facture": 1,
|
||||
"est_prospect": False,
|
||||
"bl_en_facture": 0,
|
||||
"saut_page": 0,
|
||||
"validation_echeance": 0,
|
||||
"controle_encours": 1,
|
||||
"exclure_relance": False,
|
||||
"exclure_penalites": False,
|
||||
"bon_a_payer": 0,
|
||||
"priorite_livraison": 1,
|
||||
"livraison_partielle": 1,
|
||||
"delai_transport": 2,
|
||||
"delai_appro": 0,
|
||||
"commentaire": "Client important",
|
||||
"section_analytique": "",
|
||||
"mode_reglement_code": 1,
|
||||
"surveillance_active": True,
|
||||
"coface": "COF12345",
|
||||
"forme_juridique": "SARL",
|
||||
"effectif": "50-99",
|
||||
"sv_regularite": "",
|
||||
"sv_cotation": "",
|
||||
"sv_objet_maj": "",
|
||||
"sv_chiffre_affaires": 2500000.0,
|
||||
"sv_resultat": 150000.0,
|
||||
"compte_general": "4110000",
|
||||
"categorie_tarif": 0,
|
||||
"categorie_compta": 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FournisseurCreateAPIRequest(BaseModel):
|
||||
intitule: str = Field(
|
||||
..., min_length=1, max_length=69, description="Raison sociale du fournisseur"
|
||||
)
|
||||
compte_collectif: str = Field(
|
||||
"401000", description="Compte comptable fournisseur (ex: 401000)"
|
||||
)
|
||||
num: Optional[str] = Field(
|
||||
None, max_length=17, description="Code fournisseur souhaité (optionnel)"
|
||||
)
|
||||
adresse: Optional[str] = Field(None, max_length=35)
|
||||
code_postal: Optional[str] = Field(None, max_length=9)
|
||||
ville: Optional[str] = Field(None, max_length=35)
|
||||
pays: Optional[str] = Field(None, max_length=35)
|
||||
email: Optional[EmailStr] = None
|
||||
telephone: Optional[str] = Field(None, max_length=21)
|
||||
siret: Optional[str] = Field(None, max_length=14)
|
||||
tva_intra: Optional[str] = Field(None, max_length=25)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"intitule": "ACME SUPPLIES SARL",
|
||||
"compte_collectif": "401000",
|
||||
"num": "FOUR001",
|
||||
"adresse": "15 Rue du Commerce",
|
||||
"code_postal": "75001",
|
||||
"ville": "Paris",
|
||||
"pays": "France",
|
||||
"email": "contact@acmesupplies.fr",
|
||||
"telephone": "0145678901",
|
||||
"siret": "12345678901234",
|
||||
"tva_intra": "FR12345678901",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FournisseurUpdateRequest(BaseModel):
|
||||
"""Modèle pour modification d'un fournisseur existant"""
|
||||
|
||||
intitule: Optional[str] = Field(None, min_length=1, max_length=69)
|
||||
adresse: Optional[str] = Field(None, max_length=35)
|
||||
code_postal: Optional[str] = Field(None, max_length=9)
|
||||
ville: Optional[str] = Field(None, max_length=35)
|
||||
pays: Optional[str] = Field(None, max_length=35)
|
||||
email: Optional[EmailStr] = None
|
||||
telephone: Optional[str] = Field(None, max_length=21)
|
||||
siret: Optional[str] = Field(None, max_length=14)
|
||||
tva_intra: Optional[str] = Field(None, max_length=25)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"intitule": "ACME SUPPLIES MODIFIÉ",
|
||||
"email": "nouveau@acme.fr",
|
||||
"telephone": "0198765432",
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,50 @@
|
|||
from typing import List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from schemas.tiers.contact import Contact
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
|
||||
class TypeTiersInt(IntEnum):
|
||||
"""CT_Type - Type de tiers"""
|
||||
|
||||
CLIENT = 0
|
||||
FOURNISSEUR = 1
|
||||
SALARIE = 2
|
||||
AUTRE = 3
|
||||
|
||||
|
||||
class TiersDetails(BaseModel):
|
||||
# IDENTIFICATION
|
||||
numero: Optional[str] = Field(None, description="Code tiers (CT_Num)")
|
||||
intitule: Optional[str] = Field(None, description="Raison sociale ou Nom complet (CT_Intitule)")
|
||||
type_tiers: Optional[int] = Field(None, description="Type : 0=Client, 1=Fournisseur (CT_Type)")
|
||||
qualite: Optional[str] = Field(None, description="Qualité Sage : CLI, FOU, PRO (CT_Qualite)")
|
||||
classement: Optional[str] = Field(None, description="Code de classement (CT_Classement)")
|
||||
raccourci: Optional[str] = Field(None, description="Code raccourci 7 car. (CT_Raccourci)")
|
||||
intitule: Optional[str] = Field(
|
||||
None, description="Raison sociale ou Nom complet (CT_Intitule)"
|
||||
)
|
||||
type_tiers: Optional[int] = Field(
|
||||
None, description="Type : 0=Client, 1=Fournisseur (CT_Type)"
|
||||
)
|
||||
qualite: Optional[str] = Field(
|
||||
None, description="Qualité Sage : CLI, FOU, PRO (CT_Qualite)"
|
||||
)
|
||||
classement: Optional[str] = Field(
|
||||
None, description="Code de classement (CT_Classement)"
|
||||
)
|
||||
raccourci: Optional[str] = Field(
|
||||
None, description="Code raccourci 7 car. (CT_Raccourci)"
|
||||
)
|
||||
siret: Optional[str] = Field(None, description="N° SIRET 14 chiffres (CT_Siret)")
|
||||
tva_intra: Optional[str] = Field(None, description="N° TVA intracommunautaire (CT_Identifiant)")
|
||||
tva_intra: Optional[str] = Field(
|
||||
None, description="N° TVA intracommunautaire (CT_Identifiant)"
|
||||
)
|
||||
code_naf: Optional[str] = Field(None, description="Code NAF/APE (CT_Ape)")
|
||||
|
||||
# ADRESSE
|
||||
contact: Optional[str] = Field(None, description="Nom du contact principal (CT_Contact)")
|
||||
contact: Optional[str] = Field(
|
||||
None, description="Nom du contact principal (CT_Contact)"
|
||||
)
|
||||
adresse: Optional[str] = Field(None, description="Adresse ligne 1 (CT_Adresse)")
|
||||
complement: Optional[str] = Field(None, description="Complément d'adresse (CT_Complement)")
|
||||
complement: Optional[str] = Field(
|
||||
None, description="Complément d'adresse (CT_Complement)"
|
||||
)
|
||||
code_postal: Optional[str] = Field(None, description="Code postal (CT_CodePostal)")
|
||||
ville: Optional[str] = Field(None, description="Ville (CT_Ville)")
|
||||
region: Optional[str] = Field(None, description="Région/État (CT_CodeRegion)")
|
||||
|
|
@ -38,67 +65,150 @@ class TiersDetails(BaseModel):
|
|||
taux04: Optional[float] = Field(None, description="Taux personnalisé 4 (CT_Taux04)")
|
||||
|
||||
# STATISTIQUES
|
||||
statistique01: Optional[str] = Field(None, description="Statistique 1 (CT_Statistique01)")
|
||||
statistique02: Optional[str] = Field(None, description="Statistique 2 (CT_Statistique02)")
|
||||
statistique03: Optional[str] = Field(None, description="Statistique 3 (CT_Statistique03)")
|
||||
statistique04: Optional[str] = Field(None, description="Statistique 4 (CT_Statistique04)")
|
||||
statistique05: Optional[str] = Field(None, description="Statistique 5 (CT_Statistique05)")
|
||||
statistique06: Optional[str] = Field(None, description="Statistique 6 (CT_Statistique06)")
|
||||
statistique07: Optional[str] = Field(None, description="Statistique 7 (CT_Statistique07)")
|
||||
statistique08: Optional[str] = Field(None, description="Statistique 8 (CT_Statistique08)")
|
||||
statistique09: Optional[str] = Field(None, description="Statistique 9 (CT_Statistique09)")
|
||||
statistique10: Optional[str] = Field(None, description="Statistique 10 (CT_Statistique10)")
|
||||
statistique01: Optional[str] = Field(
|
||||
None, description="Statistique 1 (CT_Statistique01)"
|
||||
)
|
||||
statistique02: Optional[str] = Field(
|
||||
None, description="Statistique 2 (CT_Statistique02)"
|
||||
)
|
||||
statistique03: Optional[str] = Field(
|
||||
None, description="Statistique 3 (CT_Statistique03)"
|
||||
)
|
||||
statistique04: Optional[str] = Field(
|
||||
None, description="Statistique 4 (CT_Statistique04)"
|
||||
)
|
||||
statistique05: Optional[str] = Field(
|
||||
None, description="Statistique 5 (CT_Statistique05)"
|
||||
)
|
||||
statistique06: Optional[str] = Field(
|
||||
None, description="Statistique 6 (CT_Statistique06)"
|
||||
)
|
||||
statistique07: Optional[str] = Field(
|
||||
None, description="Statistique 7 (CT_Statistique07)"
|
||||
)
|
||||
statistique08: Optional[str] = Field(
|
||||
None, description="Statistique 8 (CT_Statistique08)"
|
||||
)
|
||||
statistique09: Optional[str] = Field(
|
||||
None, description="Statistique 9 (CT_Statistique09)"
|
||||
)
|
||||
statistique10: Optional[str] = Field(
|
||||
None, description="Statistique 10 (CT_Statistique10)"
|
||||
)
|
||||
|
||||
# COMMERCIAL
|
||||
encours_autorise: Optional[float] = Field(None, description="Encours maximum autorisé (CT_Encours)")
|
||||
assurance_credit: Optional[float] = Field(None, description="Montant assurance crédit (CT_Assurance)")
|
||||
langue: Optional[int] = Field(None, description="Code langue 0=FR, 1=EN (CT_Langue)")
|
||||
commercial_code: Optional[int] = Field(None, description="Code du commercial (CO_No)")
|
||||
encours_autorise: Optional[float] = Field(
|
||||
None, description="Encours maximum autorisé (CT_Encours)"
|
||||
)
|
||||
assurance_credit: Optional[float] = Field(
|
||||
None, description="Montant assurance crédit (CT_Assurance)"
|
||||
)
|
||||
langue: Optional[int] = Field(
|
||||
None, description="Code langue 0=FR, 1=EN (CT_Langue)"
|
||||
)
|
||||
commercial_code: Optional[int] = Field(
|
||||
None, description="Code du commercial (CO_No)"
|
||||
)
|
||||
|
||||
# FACTURATION
|
||||
lettrage_auto: Optional[bool] = Field(None, description="Lettrage automatique (CT_Lettrage)")
|
||||
lettrage_auto: Optional[bool] = Field(
|
||||
None, description="Lettrage automatique (CT_Lettrage)"
|
||||
)
|
||||
est_actif: Optional[bool] = Field(None, description="True si actif (CT_Sommeil=0)")
|
||||
type_facture: Optional[int] = Field(None, description="Type facture 0=Facture, 1=BL (CT_Facture)")
|
||||
est_prospect: Optional[bool] = Field(None, description="True si prospect (CT_Prospect=1)")
|
||||
bl_en_facture: Optional[int] = Field(None, description="Imprimer BL en facture (CT_BLFact)")
|
||||
saut_page: Optional[int] = Field(None, description="Saut de page sur documents (CT_Saut)")
|
||||
validation_echeance: Optional[int] = Field(None, description="Valider les échéances (CT_ValidEch)")
|
||||
controle_encours: Optional[int] = Field(None, description="Contrôler l'encours (CT_ControlEnc)")
|
||||
exclure_relance: Optional[bool] = Field(None, description="Exclure des relances (CT_NotRappel)")
|
||||
exclure_penalites: Optional[bool] = Field(None, description="Exclure des pénalités (CT_NotPenal)")
|
||||
bon_a_payer: Optional[int] = Field(None, description="Bon à payer obligatoire (CT_BonAPayer)")
|
||||
type_facture: Optional[int] = Field(
|
||||
None, description="Type facture 0=Facture, 1=BL (CT_Facture)"
|
||||
)
|
||||
est_prospect: Optional[bool] = Field(
|
||||
None, description="True si prospect (CT_Prospect=1)"
|
||||
)
|
||||
bl_en_facture: Optional[int] = Field(
|
||||
None, description="Imprimer BL en facture (CT_BLFact)"
|
||||
)
|
||||
saut_page: Optional[int] = Field(
|
||||
None, description="Saut de page sur documents (CT_Saut)"
|
||||
)
|
||||
validation_echeance: Optional[int] = Field(
|
||||
None, description="Valider les échéances (CT_ValidEch)"
|
||||
)
|
||||
controle_encours: Optional[int] = Field(
|
||||
None, description="Contrôler l'encours (CT_ControlEnc)"
|
||||
)
|
||||
exclure_relance: Optional[bool] = Field(
|
||||
None, description="Exclure des relances (CT_NotRappel)"
|
||||
)
|
||||
exclure_penalites: Optional[bool] = Field(
|
||||
None, description="Exclure des pénalités (CT_NotPenal)"
|
||||
)
|
||||
bon_a_payer: Optional[int] = Field(
|
||||
None, description="Bon à payer obligatoire (CT_BonAPayer)"
|
||||
)
|
||||
|
||||
# LOGISTIQUE
|
||||
priorite_livraison: Optional[int] = Field(None, description="Priorité livraison (CT_PrioriteLivr)")
|
||||
livraison_partielle: Optional[int] = Field(None, description="Livraison partielle (CT_LivrPartielle)")
|
||||
delai_transport: Optional[int] = Field(None, description="Délai transport jours (CT_DelaiTransport)")
|
||||
delai_appro: Optional[int] = Field(None, description="Délai appro jours (CT_DelaiAppro)")
|
||||
priorite_livraison: Optional[int] = Field(
|
||||
None, description="Priorité livraison (CT_PrioriteLivr)"
|
||||
)
|
||||
livraison_partielle: Optional[int] = Field(
|
||||
None, description="Livraison partielle (CT_LivrPartielle)"
|
||||
)
|
||||
delai_transport: Optional[int] = Field(
|
||||
None, description="Délai transport jours (CT_DelaiTransport)"
|
||||
)
|
||||
delai_appro: Optional[int] = Field(
|
||||
None, description="Délai appro jours (CT_DelaiAppro)"
|
||||
)
|
||||
|
||||
# COMMENTAIRE
|
||||
commentaire: Optional[str] = Field(None, description="Commentaire libre (CT_Commentaire)")
|
||||
commentaire: Optional[str] = Field(
|
||||
None, description="Commentaire libre (CT_Commentaire)"
|
||||
)
|
||||
|
||||
# ANALYTIQUE
|
||||
section_analytique: Optional[str] = Field(None, description="Section analytique (CA_Num)")
|
||||
section_analytique: Optional[str] = Field(
|
||||
None, description="Section analytique (CA_Num)"
|
||||
)
|
||||
|
||||
# ORGANISATION / SURVEILLANCE
|
||||
mode_reglement_code: Optional[int] = Field(None, description="Code mode règlement (MR_No)")
|
||||
surveillance_active: Optional[bool] = Field(None, description="Surveillance financière (CT_Surveillance)")
|
||||
mode_reglement_code: Optional[int] = Field(
|
||||
None, description="Code mode règlement (MR_No)"
|
||||
)
|
||||
surveillance_active: Optional[bool] = Field(
|
||||
None, description="Surveillance financière (CT_Surveillance)"
|
||||
)
|
||||
coface: Optional[str] = Field(None, description="Code Coface 25 car. (CT_Coface)")
|
||||
forme_juridique: Optional[str] = Field(None, description="Forme juridique SA, SARL (CT_SvFormeJuri)")
|
||||
effectif: Optional[str] = Field(None, description="Nombre d'employés (CT_SvEffectif)")
|
||||
sv_regularite: Optional[str] = Field(None, description="Régularité paiements (CT_SvRegul)")
|
||||
sv_cotation: Optional[str] = Field(None, description="Cotation crédit (CT_SvCotation)")
|
||||
sv_objet_maj: Optional[str] = Field(None, description="Objet dernière MAJ (CT_SvObjetMaj)")
|
||||
sv_chiffre_affaires: Optional[float] = Field(None, description="Chiffre d'affaires (CT_SvCA)")
|
||||
sv_resultat: Optional[float] = Field(None, description="Résultat financier (CT_SvResultat)")
|
||||
forme_juridique: Optional[str] = Field(
|
||||
None, description="Forme juridique SA, SARL (CT_SvFormeJuri)"
|
||||
)
|
||||
effectif: Optional[str] = Field(
|
||||
None, description="Nombre d'employés (CT_SvEffectif)"
|
||||
)
|
||||
sv_regularite: Optional[str] = Field(
|
||||
None, description="Régularité paiements (CT_SvRegul)"
|
||||
)
|
||||
sv_cotation: Optional[str] = Field(
|
||||
None, description="Cotation crédit (CT_SvCotation)"
|
||||
)
|
||||
sv_objet_maj: Optional[str] = Field(
|
||||
None, description="Objet dernière MAJ (CT_SvObjetMaj)"
|
||||
)
|
||||
sv_chiffre_affaires: Optional[float] = Field(
|
||||
None, description="Chiffre d'affaires (CT_SvCA)"
|
||||
)
|
||||
sv_resultat: Optional[float] = Field(
|
||||
None, description="Résultat financier (CT_SvResultat)"
|
||||
)
|
||||
|
||||
# COMPTE GENERAL ET CATEGORIES
|
||||
compte_general: Optional[str] = Field(None, description="Compte général principal (CG_NumPrinc)")
|
||||
categorie_tarif: Optional[int] = Field(None, description="Catégorie tarifaire (N_CatTarif)")
|
||||
categorie_compta: Optional[int] = Field(None, description="Catégorie comptable (N_CatCompta)")
|
||||
|
||||
compte_general: Optional[str] = Field(
|
||||
None, description="Compte général principal (CG_NumPrinc)"
|
||||
)
|
||||
categorie_tarif: Optional[int] = Field(
|
||||
None, description="Catégorie tarifaire (N_CatTarif)"
|
||||
)
|
||||
categorie_compta: Optional[int] = Field(
|
||||
None, description="Catégorie comptable (N_CatCompta)"
|
||||
)
|
||||
|
||||
# CONTACTS
|
||||
contacts: Optional[List[Contact]] = Field(
|
||||
default_factory=list,
|
||||
description="Liste des contacts du tiers"
|
||||
)
|
||||
default_factory=list, description="Liste des contacts du tiers"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class TypeTiers(str, Enum):
|
||||
"""Types de tiers possibles"""
|
||||
|
||||
ALL = "all"
|
||||
CLIENT = "client"
|
||||
FOURNISSEUR = "fournisseur"
|
||||
PROSPECT = "prospect"
|
||||
PROSPECT = "prospect"
|
||||
|
|
|
|||
20
schemas/user.py
Normal file
20
schemas/user.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
"""Modèle de réponse pour un utilisateur"""
|
||||
|
||||
id: str
|
||||
email: str
|
||||
nom: str
|
||||
prenom: str
|
||||
role: str
|
||||
is_verified: bool
|
||||
is_active: bool
|
||||
created_at: str
|
||||
last_login: Optional[str] = None
|
||||
failed_login_attempts: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
259
utils/generic_functions.py
Normal file
259
utils/generic_functions.py
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
|
||||
from typing import Dict
|
||||
from config import settings
|
||||
import logging
|
||||
|
||||
from data.data import templates_signature_email
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def universign_envoyer(
|
||||
doc_id: str,
|
||||
pdf_bytes: bytes,
|
||||
email: str,
|
||||
nom: str,
|
||||
doc_data: Dict,
|
||||
session: AsyncSession,
|
||||
) -> Dict:
|
||||
import requests
|
||||
|
||||
try:
|
||||
api_key = settings.universign_api_key
|
||||
api_url = settings.universign_api_url
|
||||
auth = (api_key, "")
|
||||
|
||||
logger.info(f" Démarrage processus Universign pour {email}")
|
||||
logger.info(f"Document: {doc_id} ({doc_data.get('type_label')})")
|
||||
|
||||
if not pdf_bytes or len(pdf_bytes) == 0:
|
||||
raise Exception("Le PDF généré est vide")
|
||||
|
||||
logger.info(f"PDF valide : {len(pdf_bytes)} octets")
|
||||
|
||||
logger.info("ÉTAPE 1/6 : Création transaction")
|
||||
|
||||
response = requests.post(
|
||||
f"{api_url}/transactions",
|
||||
auth=auth,
|
||||
json={
|
||||
"name": f"{doc_data.get('type_label', 'Document')} {doc_id}",
|
||||
"language": "fr",
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Erreur création transaction: {response.text}")
|
||||
raise Exception(f"Erreur création transaction: {response.status_code}")
|
||||
|
||||
transaction_id = response.json().get("id")
|
||||
logger.info(f"Transaction créée: {transaction_id}")
|
||||
|
||||
logger.info("ÉTAPE 2/6 : Upload PDF")
|
||||
|
||||
files = {
|
||||
"file": (
|
||||
f"{doc_data.get('type_label', 'Document')}_{doc_id}.pdf",
|
||||
pdf_bytes,
|
||||
"application/pdf",
|
||||
)
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{api_url}/files",
|
||||
auth=auth,
|
||||
files=files,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
if response.status_code not in [200, 201]:
|
||||
logger.error(f"Erreur upload: {response.text}")
|
||||
raise Exception(f"Erreur upload fichier: {response.status_code}")
|
||||
|
||||
file_id = response.json().get("id")
|
||||
logger.info(f"Fichier uploadé: {file_id}")
|
||||
|
||||
logger.info("ÉTAPE 3/6 : Ajout document à transaction")
|
||||
|
||||
response = requests.post(
|
||||
f"{api_url}/transactions/{transaction_id}/documents",
|
||||
auth=auth,
|
||||
data={"document": file_id},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if response.status_code not in [200, 201]:
|
||||
logger.error(f"Erreur ajout document: {response.text}")
|
||||
raise Exception(f"Erreur ajout document: {response.status_code}")
|
||||
|
||||
document_id = response.json().get("id")
|
||||
logger.info(f"Document ajouté: {document_id}")
|
||||
|
||||
logger.info("ÉTAPE 4/6 : Création champ signature")
|
||||
|
||||
response = requests.post(
|
||||
f"{api_url}/transactions/{transaction_id}/documents/{document_id}/fields",
|
||||
auth=auth,
|
||||
data={
|
||||
"type": "signature",
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if response.status_code not in [200, 201]:
|
||||
logger.error(f"Erreur création champ: {response.text}")
|
||||
raise Exception(f"Erreur création champ: {response.status_code}")
|
||||
|
||||
field_id = response.json().get("id")
|
||||
logger.info(f"Champ créé: {field_id}")
|
||||
|
||||
logger.info(" ÉTAPE 5/6 : Liaison signataire au champ")
|
||||
|
||||
response = requests.post(
|
||||
f"{api_url}/transactions/{transaction_id}/signatures", # /signatures pas /signers
|
||||
auth=auth,
|
||||
data={
|
||||
"signer": email,
|
||||
"field": field_id,
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if response.status_code not in [200, 201]:
|
||||
logger.error(f"Erreur liaison signataire: {response.text}")
|
||||
raise Exception(f"Erreur liaison signataire: {response.status_code}")
|
||||
|
||||
logger.info(f"Signataire lié: {email}")
|
||||
|
||||
logger.info("ÉTAPE 6/6 : Démarrage transaction")
|
||||
|
||||
response = requests.post(
|
||||
f"{api_url}/transactions/{transaction_id}/start", auth=auth, timeout=30
|
||||
)
|
||||
|
||||
if response.status_code not in [200, 201]:
|
||||
logger.error(f"Erreur démarrage: {response.text}")
|
||||
raise Exception(f"Erreur démarrage: {response.status_code}")
|
||||
|
||||
final_data = response.json()
|
||||
logger.info("Transaction démarrée")
|
||||
|
||||
logger.info("Récupération URL de signature")
|
||||
|
||||
signer_url = ""
|
||||
|
||||
if final_data.get("actions"):
|
||||
for action in final_data["actions"]:
|
||||
if action.get("url"):
|
||||
signer_url = action["url"]
|
||||
break
|
||||
|
||||
if not signer_url and final_data.get("signers"):
|
||||
for signer in final_data["signers"]:
|
||||
if signer.get("email") == email:
|
||||
signer_url = signer.get("url", "")
|
||||
break
|
||||
|
||||
if not signer_url:
|
||||
logger.error(f"URL introuvable dans: {final_data}")
|
||||
raise ValueError("URL de signature non retournée par Universign")
|
||||
|
||||
logger.info("URL récupérée")
|
||||
|
||||
logger.info(" Préparation email")
|
||||
|
||||
template = templates_signature_email["demande_signature"]
|
||||
|
||||
type_labels = {
|
||||
0: "Devis",
|
||||
10: "Commande",
|
||||
30: "Bon de Livraison",
|
||||
60: "Facture",
|
||||
50: "Avoir",
|
||||
}
|
||||
|
||||
variables = {
|
||||
"NOM_SIGNATAIRE": nom,
|
||||
"TYPE_DOC": type_labels.get(doc_data.get("type_doc", 0), "Document"),
|
||||
"NUMERO": doc_id,
|
||||
"DATE": doc_data.get("date", datetime.now().strftime("%d/%m/%Y")),
|
||||
"MONTANT_TTC": f"{doc_data.get('montant_ttc', 0):.2f}",
|
||||
"SIGNER_URL": signer_url,
|
||||
"CONTACT_EMAIL": settings.smtp_from,
|
||||
}
|
||||
|
||||
sujet = template["sujet"]
|
||||
corps = template["corps_html"]
|
||||
|
||||
for var, valeur in variables.items():
|
||||
sujet = sujet.replace(f"{{{{{var}}}}}", str(valeur))
|
||||
corps = corps.replace(f"{{{{{var}}}}}", str(valeur))
|
||||
|
||||
email_log = EmailLog(
|
||||
id=str(uuid.uuid4()),
|
||||
destinataire=email,
|
||||
sujet=sujet,
|
||||
corps_html=corps,
|
||||
document_ids=doc_id,
|
||||
type_document=doc_data.get("type_doc"),
|
||||
statut=StatutEmailEnum.EN_ATTENTE,
|
||||
date_creation=datetime.now(),
|
||||
nb_tentatives=0,
|
||||
)
|
||||
|
||||
session.add(email_log)
|
||||
await session.flush()
|
||||
|
||||
email_queue.enqueue(email_log.id)
|
||||
|
||||
logger.info(f"Email mis en file pour {email}")
|
||||
logger.info("🎉 Processus terminé avec succès")
|
||||
|
||||
return {
|
||||
"transaction_id": transaction_id,
|
||||
"signer_url": signer_url,
|
||||
"statut": "ENVOYE",
|
||||
"email_log_id": email_log.id,
|
||||
"email_sent": True,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur Universign: {e}", exc_info=True)
|
||||
return {
|
||||
"error": str(e),
|
||||
"statut": "ERREUR",
|
||||
"email_sent": False,
|
||||
}
|
||||
|
||||
|
||||
async def universign_statut(transaction_id: str) -> Dict:
|
||||
"""Récupération statut signature"""
|
||||
import requests
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{settings.universign_api_url}/transactions/{transaction_id}",
|
||||
auth=(settings.universign_api_key, ""),
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
statut_map = {
|
||||
"draft": "EN_ATTENTE",
|
||||
"started": "EN_ATTENTE",
|
||||
"completed": "SIGNE",
|
||||
"refused": "REFUSE",
|
||||
"expired": "EXPIRE",
|
||||
"canceled": "REFUSE",
|
||||
}
|
||||
return {
|
||||
"statut": statut_map.get(data.get("state"), "EN_ATTENTE"),
|
||||
"date_signature": data.get("completed_at"),
|
||||
}
|
||||
else:
|
||||
return {"statut": "ERREUR"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur statut Universign: {e}")
|
||||
return {"statut": "ERREUR", "error": str(e)}
|
||||
Loading…
Reference in a new issue