factpulse 1.0.6__py3-none-any.whl → 2.0.37__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of factpulse might be problematic. Click here for more details.
- factpulse/__init__.py +54 -54
- factpulse/api/__init__.py +1 -2
- factpulse/api/afnorpdppa_api.py +552 -2
- factpulse/api/afnorpdppa_directory_service_api.py +4312 -65
- factpulse/api/afnorpdppa_flow_service_api.py +1 -1
- factpulse/api/chorus_pro_api.py +152 -194
- factpulse/api/sant_api.py +246 -1
- factpulse/api/traitement_facture_api.py +25 -27
- factpulse/api/utilisateur_api.py +1 -1
- factpulse/api/vrification_pdfxml_api.py +1719 -0
- factpulse/api_client.py +5 -5
- factpulse/configuration.py +5 -3
- factpulse/exceptions.py +7 -4
- factpulse/models/__init__.py +26 -25
- factpulse/models/adresse_electronique.py +1 -1
- factpulse/models/adresse_postale.py +1 -1
- factpulse/models/{body_ajouter_fichier_api_v1_chorus_pro_transverses_ajouter_fichier_post.py → api_error.py} +24 -24
- factpulse/models/{body_completer_facture_api_v1_chorus_pro_factures_completer_post.py → bounding_box_schema.py} +23 -27
- factpulse/models/cadre_de_facturation.py +11 -3
- factpulse/models/categorie_tva.py +11 -11
- factpulse/models/certificate_info_response.py +1 -1
- factpulse/models/champ_verifie_schema.py +129 -0
- factpulse/models/chorus_pro_credentials.py +1 -1
- factpulse/models/code_cadre_facturation.py +2 -2
- factpulse/models/code_raison_reduction.py +9 -9
- factpulse/models/consulter_facture_request.py +1 -1
- factpulse/models/consulter_facture_response.py +1 -1
- factpulse/models/consulter_structure_request.py +1 -1
- factpulse/models/consulter_structure_response.py +1 -1
- factpulse/models/credentials_afnor.py +1 -1
- factpulse/models/credentials_chorus_pro.py +1 -1
- factpulse/models/destinataire.py +16 -2
- factpulse/models/destination.py +1 -1
- factpulse/models/destination_afnor.py +1 -1
- factpulse/models/destination_chorus_pro.py +1 -1
- factpulse/models/{quota_info.py → dimension_page_schema.py} +12 -18
- factpulse/models/direction_flux.py +1 -1
- factpulse/models/donnees_facture_simplifiees.py +1 -1
- factpulse/models/error_level.py +37 -0
- factpulse/models/error_source.py +43 -0
- factpulse/models/{facture_enrichie_info_output.py → facture_enrichie_info.py} +4 -4
- factpulse/models/facture_entrante.py +196 -0
- factpulse/models/facture_factur_x.py +12 -2
- factpulse/models/flux_resume.py +1 -1
- factpulse/models/format_facture.py +38 -0
- factpulse/models/format_sortie.py +1 -1
- factpulse/models/fournisseur.py +9 -2
- factpulse/models/fournisseur_entrant.py +144 -0
- factpulse/models/generate_certificate_request.py +1 -1
- factpulse/models/generate_certificate_response.py +1 -1
- factpulse/models/http_validation_error.py +1 -1
- factpulse/models/information_signature_api.py +1 -1
- factpulse/models/ligne_de_poste.py +7 -12
- factpulse/models/ligne_de_poste_montant_remise_ht.py +2 -2
- factpulse/models/ligne_de_poste_taux_tva_manuel.py +2 -2
- factpulse/models/ligne_de_tva.py +24 -10
- factpulse/models/mode_depot.py +1 -1
- factpulse/models/mode_paiement.py +1 -1
- factpulse/models/{montantapayer.py → montant_a_payer.py} +6 -6
- factpulse/models/{montantbaseht.py → montant_base_ht.py} +6 -6
- factpulse/models/montant_ht_total.py +4 -4
- factpulse/models/{montant_total_montant_remise_globale_ttc.py → montant_remise_globale_ttc.py} +7 -13
- factpulse/models/montant_total.py +16 -21
- factpulse/models/montant_total_acompte.py +2 -2
- factpulse/models/{ligne_de_poste_montant_total_ligne_ht.py → montant_total_ligne_ht.py} +7 -13
- factpulse/models/montant_ttc_total.py +4 -4
- factpulse/models/montant_tva.py +2 -2
- factpulse/models/{montanttva1.py → montant_tva_ligne.py} +7 -7
- factpulse/models/{montantttctotal.py → montant_tva_total.py} +7 -7
- factpulse/models/{montantunitaireht.py → montant_unitaire_ht.py} +5 -5
- factpulse/models/nature_operation.py +49 -0
- factpulse/models/{body_valideur_rechercher_factures_api_v1_chorus_pro_factures_valideur_rechercher_post.py → note.py} +14 -24
- factpulse/models/{utilisateur.py → note_obligatoire_schema.py} +40 -44
- factpulse/models/obtenir_id_chorus_pro_request.py +1 -1
- factpulse/models/obtenir_id_chorus_pro_response.py +1 -1
- factpulse/models/options_processing.py +5 -14
- factpulse/models/parametres_signature.py +1 -1
- factpulse/models/parametres_structure.py +1 -1
- factpulse/models/pdf_factur_x_info.py +1 -1
- factpulse/models/pdp_credentials.py +10 -3
- factpulse/models/piece_jointe_complementaire.py +1 -1
- factpulse/models/profil_api.py +1 -1
- factpulse/models/profil_flux.py +1 -1
- factpulse/models/quantite.py +1 -1
- factpulse/models/rechercher_services_response.py +1 -1
- factpulse/models/rechercher_structure_request.py +1 -1
- factpulse/models/rechercher_structure_response.py +1 -1
- factpulse/models/references.py +1 -1
- factpulse/models/reponse_healthcheck_afnor.py +1 -1
- factpulse/models/reponse_recherche_flux.py +1 -1
- factpulse/models/reponse_soumission_flux.py +1 -1
- factpulse/models/reponse_tache.py +1 -1
- factpulse/models/reponse_validation_erreur.py +1 -1
- factpulse/models/reponse_validation_succes.py +1 -1
- factpulse/models/reponse_verification_succes.py +135 -0
- factpulse/models/requete_recherche_flux.py +1 -1
- factpulse/models/requete_soumission_flux.py +1 -1
- factpulse/models/resultat_afnor.py +1 -1
- factpulse/models/resultat_chorus_pro.py +1 -1
- factpulse/models/resultat_validation_pdfapi.py +1 -1
- factpulse/models/scheme_id.py +7 -7
- factpulse/models/service_structure.py +1 -1
- factpulse/models/signature_info.py +1 -1
- factpulse/models/soumettre_facture_complete_request.py +1 -1
- factpulse/models/soumettre_facture_complete_response.py +4 -4
- factpulse/models/soumettre_facture_request.py +19 -7
- factpulse/models/soumettre_facture_response.py +1 -1
- factpulse/models/statut_acquittement.py +1 -1
- factpulse/models/statut_celery.py +40 -0
- factpulse/models/statut_champ_api.py +40 -0
- factpulse/models/statut_facture.py +1 -1
- factpulse/models/statut_tache.py +7 -9
- factpulse/models/structure_info.py +1 -1
- factpulse/models/syntaxe_flux.py +1 -1
- factpulse/models/tauxmanuel.py +2 -2
- factpulse/models/type_document.py +40 -0
- factpulse/models/type_facture.py +1 -1
- factpulse/models/type_flux.py +1 -1
- factpulse/models/type_tva.py +1 -1
- factpulse/models/unite.py +1 -1
- factpulse/models/validation_error.py +1 -1
- factpulse/models/{body_rechercher_factures_fournisseur_api_v1_chorus_pro_factures_rechercher_fournisseur_post.py → validation_error_detail.py} +27 -24
- factpulse/models/validation_error_loc_inner.py +1 -1
- factpulse/rest.py +8 -3
- factpulse-2.0.37.dist-info/METADATA +292 -0
- factpulse-2.0.37.dist-info/RECORD +134 -0
- factpulse-2.0.37.dist-info/top_level.txt +2 -0
- factpulse_helpers/__init__.py +96 -0
- factpulse_helpers/client.py +1887 -0
- factpulse_helpers/exceptions.py +253 -0
- factpulse/api/processing_endpoints_unifis_api.py +0 -592
- factpulse/api/signature_lectronique_api.py +0 -1358
- factpulse/models/body_lister_services_structure_api_v1_chorus_pro_structures_id_structure_cpp_services_get.py +0 -102
- factpulse/models/body_rechercher_factures_destinataire_api_v1_chorus_pro_factures_rechercher_destinataire_post.py +0 -104
- factpulse/models/body_recycler_facture_api_v1_chorus_pro_factures_recycler_post.py +0 -104
- factpulse/models/body_telecharger_groupe_factures_api_v1_chorus_pro_factures_telecharger_groupe_post.py +0 -104
- factpulse/models/body_traiter_facture_recue_api_v1_chorus_pro_factures_traiter_facture_recue_post.py +0 -104
- factpulse/models/body_valideur_consulter_facture_api_v1_chorus_pro_factures_valideur_consulter_post.py +0 -104
- factpulse/models/body_valideur_traiter_facture_api_v1_chorus_pro_factures_valideur_traiter_post.py +0 -104
- factpulse/models/facture_enrichie_info_input.py +0 -123
- factpulse/models/montanthttotal.py +0 -139
- factpulse/models/montanttva.py +0 -139
- factpulse-1.0.6.dist-info/METADATA +0 -182
- factpulse-1.0.6.dist-info/RECORD +0 -131
- factpulse-1.0.6.dist-info/top_level.txt +0 -1
- {factpulse-1.0.6.dist-info → factpulse-2.0.37.dist-info}/WHEEL +0 -0
- {factpulse-1.0.6.dist-info → factpulse-2.0.37.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Exceptions personnalisées pour le client FactPulse.
|
|
2
|
+
|
|
3
|
+
Ce module définit une hiérarchie d'exceptions alignée sur le format d'erreur
|
|
4
|
+
de l'API FactPulse (APIError, ValidationErrorDetail) conforme à la norme AFNOR.
|
|
5
|
+
|
|
6
|
+
Hiérarchie des exceptions:
|
|
7
|
+
- FactPulseError (base)
|
|
8
|
+
├── FactPulseAuthError (401)
|
|
9
|
+
├── FactPulseValidationError (400, 422) - avec détails structurés
|
|
10
|
+
├── FactPulsePollingTimeout (timeout polling)
|
|
11
|
+
├── FactPulseNotFoundError (404)
|
|
12
|
+
├── FactPulseServiceUnavailableError (503)
|
|
13
|
+
└── FactPulseAPIError (générique avec error_code)
|
|
14
|
+
"""
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FactPulseError(Exception):
|
|
20
|
+
"""Classe de base pour toutes les erreurs FactPulse."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FactPulseAuthError(FactPulseError):
|
|
25
|
+
"""Erreur d'authentification FactPulse (401).
|
|
26
|
+
|
|
27
|
+
Levée quand:
|
|
28
|
+
- Email/mot de passe invalides
|
|
29
|
+
- Token JWT expiré ou invalide
|
|
30
|
+
- client_uid non trouvé
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, message: str = "Authentification requise"):
|
|
33
|
+
self.message = message
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FactPulsePollingTimeout(FactPulseError):
|
|
38
|
+
"""Timeout lors du polling d'une tâche asynchrone."""
|
|
39
|
+
def __init__(self, task_id: str, timeout: int):
|
|
40
|
+
self.task_id = task_id
|
|
41
|
+
self.timeout = timeout
|
|
42
|
+
super().__init__(f"Timeout ({timeout}ms) atteint pour la tâche {task_id}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ValidationErrorDetail:
|
|
47
|
+
"""Détail d'une erreur de validation au format AFNOR.
|
|
48
|
+
|
|
49
|
+
Aligné sur le schéma AcknowledgementDetail de la norme AFNOR XP Z12-013.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
level: Niveau de gravité ('Error' ou 'Warning')
|
|
53
|
+
item: Identifiant de l'élément concerné (règle BR-FR, champ, XPath)
|
|
54
|
+
reason: Description de l'erreur
|
|
55
|
+
source: Source de l'erreur (schematron, pydantic, pdfa, afnor, chorus_pro)
|
|
56
|
+
code: Code d'erreur unique (ex: SCHEMATRON_BR_FR_01)
|
|
57
|
+
"""
|
|
58
|
+
level: str = ""
|
|
59
|
+
item: str = ""
|
|
60
|
+
reason: str = ""
|
|
61
|
+
source: Optional[str] = None
|
|
62
|
+
code: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str:
|
|
65
|
+
item = self.item or "unknown"
|
|
66
|
+
reason = self.reason or "Unknown error"
|
|
67
|
+
source_str = f" [{self.source}]" if self.source else ""
|
|
68
|
+
return f"[{item}]{source_str} {reason}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class FactPulseValidationError(FactPulseError):
|
|
72
|
+
"""Erreur de validation avec détails structurés (400, 422).
|
|
73
|
+
|
|
74
|
+
Contient une liste de ValidationErrorDetail pour le diagnostic.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
errors: Liste des erreurs détaillées
|
|
78
|
+
error_code: Code d'erreur API (ex: VALIDATION_FAILED, SCHEMATRON_VALIDATION_FAILED)
|
|
79
|
+
"""
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
message: str,
|
|
83
|
+
errors: Optional[List[ValidationErrorDetail]] = None,
|
|
84
|
+
error_code: str = "VALIDATION_FAILED",
|
|
85
|
+
):
|
|
86
|
+
self.errors = errors or []
|
|
87
|
+
self.error_code = error_code
|
|
88
|
+
if self.errors:
|
|
89
|
+
details = "\n".join(f" - {e}" for e in self.errors)
|
|
90
|
+
message = f"{message}\n\nDétails:\n{details}"
|
|
91
|
+
super().__init__(message)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class FactPulseNotFoundError(FactPulseError):
|
|
95
|
+
"""Ressource non trouvée (404).
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
resource: Type de ressource (facture, structure, flux, client)
|
|
99
|
+
identifier: Identifiant de la ressource
|
|
100
|
+
"""
|
|
101
|
+
def __init__(self, resource: str, identifier: str = ""):
|
|
102
|
+
self.resource = resource
|
|
103
|
+
self.identifier = identifier
|
|
104
|
+
message = f"{resource.capitalize()} non trouvé(e)"
|
|
105
|
+
if identifier:
|
|
106
|
+
message = f"{resource.capitalize()} '{identifier}' non trouvé(e)"
|
|
107
|
+
super().__init__(message)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class FactPulseServiceUnavailableError(FactPulseError):
|
|
111
|
+
"""Service externe indisponible (503).
|
|
112
|
+
|
|
113
|
+
Attributes:
|
|
114
|
+
service_name: Nom du service (AFNOR PDP, Chorus Pro, Django)
|
|
115
|
+
original_error: Exception originale (optionnel)
|
|
116
|
+
"""
|
|
117
|
+
def __init__(self, service_name: str, original_error: Optional[Exception] = None):
|
|
118
|
+
self.service_name = service_name
|
|
119
|
+
self.original_error = original_error
|
|
120
|
+
message = f"Le service {service_name} est indisponible"
|
|
121
|
+
if original_error:
|
|
122
|
+
message = f"{message}: {str(original_error)}"
|
|
123
|
+
super().__init__(message)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class FactPulseAPIError(FactPulseError):
|
|
127
|
+
"""Erreur API générique avec code d'erreur structuré.
|
|
128
|
+
|
|
129
|
+
Utilisée pour les erreurs non couvertes par les exceptions spécifiques.
|
|
130
|
+
|
|
131
|
+
Attributes:
|
|
132
|
+
status_code: Code HTTP de la réponse
|
|
133
|
+
error_code: Code d'erreur API (ex: INTERNAL_ERROR)
|
|
134
|
+
error_message: Message d'erreur de l'API
|
|
135
|
+
details: Détails optionnels (ValidationErrorDetail)
|
|
136
|
+
"""
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
status_code: int,
|
|
140
|
+
error_code: str,
|
|
141
|
+
error_message: str,
|
|
142
|
+
details: Optional[List[ValidationErrorDetail]] = None,
|
|
143
|
+
):
|
|
144
|
+
self.status_code = status_code
|
|
145
|
+
self.error_code = error_code
|
|
146
|
+
self.error_message = error_message
|
|
147
|
+
self.details = details or []
|
|
148
|
+
super().__init__(f"[{error_code}] {error_message}")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def parse_api_error(response_json: Dict[str, Any], status_code: int = 400) -> FactPulseError:
|
|
152
|
+
"""Parse une réponse d'erreur API et retourne l'exception appropriée.
|
|
153
|
+
|
|
154
|
+
Cette fonction parse le format d'erreur unifié de l'API FactPulse
|
|
155
|
+
(APIError avec errorCode, errorMessage, details) et retourne
|
|
156
|
+
l'exception Python appropriée.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
response_json: JSON de la réponse d'erreur (dict)
|
|
160
|
+
status_code: Code HTTP de la réponse
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Exception appropriée selon le status_code et error_code
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> response = requests.post(url, json=data)
|
|
167
|
+
>>> if response.status_code >= 400:
|
|
168
|
+
... error = parse_api_error(response.json(), response.status_code)
|
|
169
|
+
... raise error
|
|
170
|
+
"""
|
|
171
|
+
# Extraire les champs de l'erreur API
|
|
172
|
+
# Support des deux formats : camelCase (API) et snake_case
|
|
173
|
+
error_code = response_json.get("errorCode") or response_json.get("error_code") or "UNKNOWN_ERROR"
|
|
174
|
+
error_message = response_json.get("errorMessage") or response_json.get("error_message") or "Erreur inconnue"
|
|
175
|
+
details_raw = response_json.get("details") or []
|
|
176
|
+
|
|
177
|
+
# Parfois l'erreur est dans un wrapper "detail"
|
|
178
|
+
if "detail" in response_json and isinstance(response_json["detail"], dict):
|
|
179
|
+
detail = response_json["detail"]
|
|
180
|
+
error_code = detail.get("error") or detail.get("errorCode") or error_code
|
|
181
|
+
error_message = detail.get("message") or detail.get("errorMessage") or error_message
|
|
182
|
+
details_raw = detail.get("details") or details_raw
|
|
183
|
+
|
|
184
|
+
# Parser les détails en ValidationErrorDetail
|
|
185
|
+
details = []
|
|
186
|
+
for d in details_raw:
|
|
187
|
+
if isinstance(d, dict):
|
|
188
|
+
details.append(ValidationErrorDetail(
|
|
189
|
+
level=d.get("level", "Error"),
|
|
190
|
+
item=d.get("item", ""),
|
|
191
|
+
reason=d.get("reason", ""),
|
|
192
|
+
source=d.get("source"),
|
|
193
|
+
code=d.get("code"),
|
|
194
|
+
))
|
|
195
|
+
|
|
196
|
+
# Retourner l'exception appropriée selon le status_code
|
|
197
|
+
if status_code == 401:
|
|
198
|
+
return FactPulseAuthError(error_message)
|
|
199
|
+
elif status_code == 404:
|
|
200
|
+
# Essayer d'extraire la ressource depuis le message
|
|
201
|
+
resource = "ressource"
|
|
202
|
+
if "client" in error_message.lower():
|
|
203
|
+
resource = "client"
|
|
204
|
+
elif "flux" in error_message.lower() or "flow" in error_message.lower():
|
|
205
|
+
resource = "flux"
|
|
206
|
+
elif "facture" in error_message.lower():
|
|
207
|
+
resource = "facture"
|
|
208
|
+
elif "structure" in error_message.lower():
|
|
209
|
+
resource = "structure"
|
|
210
|
+
return FactPulseNotFoundError(resource)
|
|
211
|
+
elif status_code == 503:
|
|
212
|
+
service_name = "API"
|
|
213
|
+
if "afnor" in error_message.lower() or "pdp" in error_message.lower():
|
|
214
|
+
service_name = "AFNOR PDP"
|
|
215
|
+
elif "chorus" in error_message.lower():
|
|
216
|
+
service_name = "Chorus Pro"
|
|
217
|
+
return FactPulseServiceUnavailableError(service_name)
|
|
218
|
+
elif status_code in (400, 422) and details:
|
|
219
|
+
return FactPulseValidationError(error_message, details, error_code)
|
|
220
|
+
else:
|
|
221
|
+
return FactPulseAPIError(status_code, error_code, error_message, details)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def api_exception_to_validation_error(api_exception) -> FactPulseValidationError:
|
|
225
|
+
"""Convertit une ApiException du SDK généré en FactPulseValidationError.
|
|
226
|
+
|
|
227
|
+
Le SDK openapi-generator génère des exceptions ApiException qui ne sont
|
|
228
|
+
pas très pratiques à utiliser. Cette fonction les convertit en exceptions
|
|
229
|
+
FactPulse avec parsing intelligent des erreurs.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
api_exception: Exception ApiException du SDK généré
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
FactPulseValidationError avec détails structurés
|
|
236
|
+
"""
|
|
237
|
+
import json
|
|
238
|
+
|
|
239
|
+
status_code = getattr(api_exception, "status", 400)
|
|
240
|
+
body = getattr(api_exception, "body", "{}")
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
response_json = json.loads(body) if isinstance(body, str) else body
|
|
244
|
+
except (json.JSONDecodeError, TypeError):
|
|
245
|
+
response_json = {"errorMessage": str(api_exception)}
|
|
246
|
+
|
|
247
|
+
error = parse_api_error(response_json, status_code)
|
|
248
|
+
|
|
249
|
+
# Convertir en FactPulseValidationError si ce n'est pas déjà le cas
|
|
250
|
+
if isinstance(error, FactPulseValidationError):
|
|
251
|
+
return error
|
|
252
|
+
else:
|
|
253
|
+
return FactPulseValidationError(str(error))
|