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.tiers import (
|
||||||
from schemas.tiers.type_tiers import (TypeTiers,)
|
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__ = [
|
__all__ = [
|
||||||
"TiersDetails",
|
"TiersDetails",
|
||||||
"TypeTiers",
|
"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 pydantic import BaseModel, Field, validator
|
||||||
|
from typing import Optional, ClassVar
|
||||||
|
|
||||||
|
|
||||||
class Contact(BaseModel):
|
class Contact(BaseModel):
|
||||||
"""Contact associé à un tiers"""
|
"""Contact associé à un tiers"""
|
||||||
|
|
||||||
numero: Optional[str] = Field(None, description="Code du tiers parent (CT_Num)")
|
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)")
|
contact_numero: Optional[int] = Field(
|
||||||
n_contact: Optional[int] = Field(None, description="Numéro de référence contact (N_Contact)")
|
None, description="Numéro unique du contact (CT_No)"
|
||||||
|
)
|
||||||
civilite: Optional[str] = Field(None, description="Civilité : M., Mme, Mlle (CT_Civilite)")
|
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)")
|
nom: Optional[str] = Field(None, description="Nom de famille (CT_Nom)")
|
||||||
prenom: Optional[str] = Field(None, description="Prénom (CT_Prenom)")
|
prenom: Optional[str] = Field(None, description="Prénom (CT_Prenom)")
|
||||||
fonction: Optional[str] = Field(None, description="Fonction/Titre (CT_Fonction)")
|
fonction: Optional[str] = Field(None, description="Fonction/Titre (CT_Fonction)")
|
||||||
|
|
||||||
service_code: Optional[int] = Field(None, description="Code du service (N_Service)")
|
service_code: Optional[int] = Field(None, description="Code du service (N_Service)")
|
||||||
|
|
||||||
telephone: Optional[str] = Field(None, description="Téléphone fixe (CT_Telephone)")
|
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)")
|
telecopie: Optional[str] = Field(None, description="Fax (CT_Telecopie)")
|
||||||
email: Optional[str] = Field(None, description="Adresse email (CT_EMail)")
|
email: Optional[str] = Field(None, description="Adresse email (CT_EMail)")
|
||||||
|
|
||||||
facebook: Optional[str] = Field(None, description="Profil Facebook (CT_Facebook)")
|
facebook: Optional[str] = Field(None, description="Profil Facebook (CT_Facebook)")
|
||||||
linkedin: Optional[str] = Field(None, description="Profil LinkedIn (CT_LinkedIn)")
|
linkedin: Optional[str] = Field(None, description="Profil LinkedIn (CT_LinkedIn)")
|
||||||
skype: Optional[str] = Field(None, description="Identifiant Skype (CT_Skype)")
|
skype: Optional[str] = Field(None, description="Identifiant Skype (CT_Skype)")
|
||||||
|
|
||||||
est_defaut: Optional[bool] = Field(False, description="Contact par défaut")
|
est_defaut: Optional[bool] = Field(False, description="Contact par défaut")
|
||||||
|
|
||||||
civilite_map: ClassVar[dict] = {
|
civilite_map: ClassVar[dict] = {
|
||||||
0: "M.",
|
0: "M.",
|
||||||
1: "Mme",
|
1: "Mme",
|
||||||
|
|
@ -38,4 +48,70 @@ class Contact(BaseModel):
|
||||||
return v
|
return v
|
||||||
if isinstance(v, int):
|
if isinstance(v, int):
|
||||||
return cls.civilite_map.get(v, str(v))
|
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 typing import List, Optional
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from schemas.tiers.contact import Contact
|
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):
|
class TiersDetails(BaseModel):
|
||||||
# IDENTIFICATION
|
# IDENTIFICATION
|
||||||
numero: Optional[str] = Field(None, description="Code tiers (CT_Num)")
|
numero: Optional[str] = Field(None, description="Code tiers (CT_Num)")
|
||||||
intitule: Optional[str] = Field(None, description="Raison sociale ou Nom complet (CT_Intitule)")
|
intitule: Optional[str] = Field(
|
||||||
type_tiers: Optional[int] = Field(None, description="Type : 0=Client, 1=Fournisseur (CT_Type)")
|
None, description="Raison sociale ou Nom complet (CT_Intitule)"
|
||||||
qualite: Optional[str] = Field(None, description="Qualité Sage : CLI, FOU, PRO (CT_Qualite)")
|
)
|
||||||
classement: Optional[str] = Field(None, description="Code de classement (CT_Classement)")
|
type_tiers: Optional[int] = Field(
|
||||||
raccourci: Optional[str] = Field(None, description="Code raccourci 7 car. (CT_Raccourci)")
|
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)")
|
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)")
|
code_naf: Optional[str] = Field(None, description="Code NAF/APE (CT_Ape)")
|
||||||
|
|
||||||
# ADRESSE
|
# 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)")
|
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)")
|
code_postal: Optional[str] = Field(None, description="Code postal (CT_CodePostal)")
|
||||||
ville: Optional[str] = Field(None, description="Ville (CT_Ville)")
|
ville: Optional[str] = Field(None, description="Ville (CT_Ville)")
|
||||||
region: Optional[str] = Field(None, description="Région/État (CT_CodeRegion)")
|
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)")
|
taux04: Optional[float] = Field(None, description="Taux personnalisé 4 (CT_Taux04)")
|
||||||
|
|
||||||
# STATISTIQUES
|
# STATISTIQUES
|
||||||
statistique01: Optional[str] = Field(None, description="Statistique 1 (CT_Statistique01)")
|
statistique01: Optional[str] = Field(
|
||||||
statistique02: Optional[str] = Field(None, description="Statistique 2 (CT_Statistique02)")
|
None, description="Statistique 1 (CT_Statistique01)"
|
||||||
statistique03: Optional[str] = Field(None, description="Statistique 3 (CT_Statistique03)")
|
)
|
||||||
statistique04: Optional[str] = Field(None, description="Statistique 4 (CT_Statistique04)")
|
statistique02: Optional[str] = Field(
|
||||||
statistique05: Optional[str] = Field(None, description="Statistique 5 (CT_Statistique05)")
|
None, description="Statistique 2 (CT_Statistique02)"
|
||||||
statistique06: Optional[str] = Field(None, description="Statistique 6 (CT_Statistique06)")
|
)
|
||||||
statistique07: Optional[str] = Field(None, description="Statistique 7 (CT_Statistique07)")
|
statistique03: Optional[str] = Field(
|
||||||
statistique08: Optional[str] = Field(None, description="Statistique 8 (CT_Statistique08)")
|
None, description="Statistique 3 (CT_Statistique03)"
|
||||||
statistique09: Optional[str] = Field(None, description="Statistique 9 (CT_Statistique09)")
|
)
|
||||||
statistique10: Optional[str] = Field(None, description="Statistique 10 (CT_Statistique10)")
|
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
|
# COMMERCIAL
|
||||||
encours_autorise: Optional[float] = Field(None, description="Encours maximum autorisé (CT_Encours)")
|
encours_autorise: Optional[float] = Field(
|
||||||
assurance_credit: Optional[float] = Field(None, description="Montant assurance crédit (CT_Assurance)")
|
None, description="Encours maximum autorisé (CT_Encours)"
|
||||||
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)")
|
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
|
# 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)")
|
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)")
|
type_facture: Optional[int] = Field(
|
||||||
est_prospect: Optional[bool] = Field(None, description="True si prospect (CT_Prospect=1)")
|
None, description="Type facture 0=Facture, 1=BL (CT_Facture)"
|
||||||
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)")
|
est_prospect: Optional[bool] = Field(
|
||||||
validation_echeance: Optional[int] = Field(None, description="Valider les échéances (CT_ValidEch)")
|
None, description="True si prospect (CT_Prospect=1)"
|
||||||
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)")
|
bl_en_facture: Optional[int] = Field(
|
||||||
exclure_penalites: Optional[bool] = Field(None, description="Exclure des pénalités (CT_NotPenal)")
|
None, description="Imprimer BL en facture (CT_BLFact)"
|
||||||
bon_a_payer: Optional[int] = Field(None, description="Bon à payer obligatoire (CT_BonAPayer)")
|
)
|
||||||
|
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
|
# LOGISTIQUE
|
||||||
priorite_livraison: Optional[int] = Field(None, description="Priorité livraison (CT_PrioriteLivr)")
|
priorite_livraison: Optional[int] = Field(
|
||||||
livraison_partielle: Optional[int] = Field(None, description="Livraison partielle (CT_LivrPartielle)")
|
None, description="Priorité livraison (CT_PrioriteLivr)"
|
||||||
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)")
|
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
|
||||||
commentaire: Optional[str] = Field(None, description="Commentaire libre (CT_Commentaire)")
|
commentaire: Optional[str] = Field(
|
||||||
|
None, description="Commentaire libre (CT_Commentaire)"
|
||||||
|
)
|
||||||
|
|
||||||
# ANALYTIQUE
|
# 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
|
# ORGANISATION / SURVEILLANCE
|
||||||
mode_reglement_code: Optional[int] = Field(None, description="Code mode règlement (MR_No)")
|
mode_reglement_code: Optional[int] = Field(
|
||||||
surveillance_active: Optional[bool] = Field(None, description="Surveillance financière (CT_Surveillance)")
|
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)")
|
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)")
|
forme_juridique: Optional[str] = Field(
|
||||||
effectif: Optional[str] = Field(None, description="Nombre d'employés (CT_SvEffectif)")
|
None, description="Forme juridique SA, SARL (CT_SvFormeJuri)"
|
||||||
sv_regularite: Optional[str] = Field(None, description="Régularité paiements (CT_SvRegul)")
|
)
|
||||||
sv_cotation: Optional[str] = Field(None, description="Cotation crédit (CT_SvCotation)")
|
effectif: Optional[str] = Field(
|
||||||
sv_objet_maj: Optional[str] = Field(None, description="Objet dernière MAJ (CT_SvObjetMaj)")
|
None, description="Nombre d'employés (CT_SvEffectif)"
|
||||||
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)")
|
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 ET CATEGORIES
|
||||||
compte_general: Optional[str] = Field(None, description="Compte général principal (CG_NumPrinc)")
|
compte_general: Optional[str] = Field(
|
||||||
categorie_tarif: Optional[int] = Field(None, description="Catégorie tarifaire (N_CatTarif)")
|
None, description="Compte général principal (CG_NumPrinc)"
|
||||||
categorie_compta: Optional[int] = Field(None, description="Catégorie comptable (N_CatCompta)")
|
)
|
||||||
|
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
|
||||||
contacts: Optional[List[Contact]] = Field(
|
contacts: Optional[List[Contact]] = Field(
|
||||||
default_factory=list,
|
default_factory=list, description="Liste des contacts du tiers"
|
||||||
description="Liste des contacts du tiers"
|
)
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class TypeTiers(str, Enum):
|
class TypeTiers(str, Enum):
|
||||||
"""Types de tiers possibles"""
|
"""Types de tiers possibles"""
|
||||||
|
|
||||||
ALL = "all"
|
ALL = "all"
|
||||||
CLIENT = "client"
|
CLIENT = "client"
|
||||||
FOURNISSEUR = "fournisseur"
|
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