factpulse 1.0.9__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.

Files changed (147) hide show
  1. factpulse/__init__.py +54 -50
  2. factpulse/api/__init__.py +1 -0
  3. factpulse/api/afnorpdppa_api.py +552 -2
  4. factpulse/api/afnorpdppa_directory_service_api.py +4312 -65
  5. factpulse/api/afnorpdppa_flow_service_api.py +1 -1
  6. factpulse/api/chorus_pro_api.py +152 -194
  7. factpulse/api/sant_api.py +246 -1
  8. factpulse/api/traitement_facture_api.py +25 -27
  9. factpulse/api/utilisateur_api.py +1 -1
  10. factpulse/api/vrification_pdfxml_api.py +1719 -0
  11. factpulse/api_client.py +5 -5
  12. factpulse/configuration.py +5 -3
  13. factpulse/exceptions.py +7 -4
  14. factpulse/models/__init__.py +26 -25
  15. factpulse/models/adresse_electronique.py +1 -1
  16. factpulse/models/adresse_postale.py +1 -1
  17. factpulse/models/{body_ajouter_fichier_api_v1_chorus_pro_transverses_ajouter_fichier_post.py → api_error.py} +24 -24
  18. factpulse/models/{body_completer_facture_api_v1_chorus_pro_factures_completer_post.py → bounding_box_schema.py} +23 -27
  19. factpulse/models/cadre_de_facturation.py +11 -3
  20. factpulse/models/categorie_tva.py +11 -11
  21. factpulse/models/certificate_info_response.py +1 -1
  22. factpulse/models/champ_verifie_schema.py +129 -0
  23. factpulse/models/chorus_pro_credentials.py +1 -1
  24. factpulse/models/code_cadre_facturation.py +2 -2
  25. factpulse/models/code_raison_reduction.py +9 -9
  26. factpulse/models/consulter_facture_request.py +1 -1
  27. factpulse/models/consulter_facture_response.py +1 -1
  28. factpulse/models/consulter_structure_request.py +1 -1
  29. factpulse/models/consulter_structure_response.py +1 -1
  30. factpulse/models/credentials_afnor.py +1 -1
  31. factpulse/models/credentials_chorus_pro.py +1 -1
  32. factpulse/models/destinataire.py +16 -2
  33. factpulse/models/destination.py +1 -1
  34. factpulse/models/destination_afnor.py +1 -1
  35. factpulse/models/destination_chorus_pro.py +1 -1
  36. factpulse/models/{quota_info.py → dimension_page_schema.py} +12 -18
  37. factpulse/models/direction_flux.py +1 -1
  38. factpulse/models/donnees_facture_simplifiees.py +1 -1
  39. factpulse/models/error_level.py +37 -0
  40. factpulse/models/error_source.py +43 -0
  41. factpulse/models/{facture_enrichie_info_output.py → facture_enrichie_info.py} +4 -4
  42. factpulse/models/facture_entrante.py +196 -0
  43. factpulse/models/facture_factur_x.py +12 -2
  44. factpulse/models/flux_resume.py +1 -1
  45. factpulse/models/format_facture.py +38 -0
  46. factpulse/models/format_sortie.py +1 -1
  47. factpulse/models/fournisseur.py +9 -2
  48. factpulse/models/fournisseur_entrant.py +144 -0
  49. factpulse/models/generate_certificate_request.py +1 -1
  50. factpulse/models/generate_certificate_response.py +1 -1
  51. factpulse/models/http_validation_error.py +1 -1
  52. factpulse/models/information_signature_api.py +1 -1
  53. factpulse/models/ligne_de_poste.py +7 -12
  54. factpulse/models/ligne_de_poste_montant_remise_ht.py +2 -2
  55. factpulse/models/ligne_de_poste_taux_tva_manuel.py +2 -2
  56. factpulse/models/ligne_de_tva.py +24 -10
  57. factpulse/models/mode_depot.py +1 -1
  58. factpulse/models/mode_paiement.py +1 -1
  59. factpulse/models/{montantapayer.py → montant_a_payer.py} +6 -6
  60. factpulse/models/{montantbaseht.py → montant_base_ht.py} +6 -6
  61. factpulse/models/montant_ht_total.py +4 -4
  62. factpulse/models/{montant_total_montant_remise_globale_ttc.py → montant_remise_globale_ttc.py} +7 -13
  63. factpulse/models/montant_total.py +16 -21
  64. factpulse/models/montant_total_acompte.py +2 -2
  65. factpulse/models/{ligne_de_poste_montant_total_ligne_ht.py → montant_total_ligne_ht.py} +7 -13
  66. factpulse/models/montant_ttc_total.py +4 -4
  67. factpulse/models/montant_tva.py +2 -2
  68. factpulse/models/{montanttva1.py → montant_tva_ligne.py} +7 -7
  69. factpulse/models/{montantttctotal.py → montant_tva_total.py} +7 -7
  70. factpulse/models/{montantunitaireht.py → montant_unitaire_ht.py} +5 -5
  71. factpulse/models/nature_operation.py +49 -0
  72. factpulse/models/{body_valideur_rechercher_factures_api_v1_chorus_pro_factures_valideur_rechercher_post.py → note.py} +14 -24
  73. factpulse/models/{utilisateur.py → note_obligatoire_schema.py} +40 -44
  74. factpulse/models/obtenir_id_chorus_pro_request.py +1 -1
  75. factpulse/models/obtenir_id_chorus_pro_response.py +1 -1
  76. factpulse/models/options_processing.py +1 -1
  77. factpulse/models/parametres_signature.py +1 -1
  78. factpulse/models/parametres_structure.py +1 -1
  79. factpulse/models/pdf_factur_x_info.py +1 -1
  80. factpulse/models/pdp_credentials.py +10 -3
  81. factpulse/models/piece_jointe_complementaire.py +1 -1
  82. factpulse/models/profil_api.py +1 -1
  83. factpulse/models/profil_flux.py +1 -1
  84. factpulse/models/quantite.py +1 -1
  85. factpulse/models/rechercher_services_response.py +1 -1
  86. factpulse/models/rechercher_structure_request.py +1 -1
  87. factpulse/models/rechercher_structure_response.py +1 -1
  88. factpulse/models/references.py +1 -1
  89. factpulse/models/reponse_healthcheck_afnor.py +1 -1
  90. factpulse/models/reponse_recherche_flux.py +1 -1
  91. factpulse/models/reponse_soumission_flux.py +1 -1
  92. factpulse/models/reponse_tache.py +1 -1
  93. factpulse/models/reponse_validation_erreur.py +1 -1
  94. factpulse/models/reponse_validation_succes.py +1 -1
  95. factpulse/models/reponse_verification_succes.py +135 -0
  96. factpulse/models/requete_recherche_flux.py +1 -1
  97. factpulse/models/requete_soumission_flux.py +1 -1
  98. factpulse/models/resultat_afnor.py +1 -1
  99. factpulse/models/resultat_chorus_pro.py +1 -1
  100. factpulse/models/resultat_validation_pdfapi.py +1 -1
  101. factpulse/models/scheme_id.py +7 -7
  102. factpulse/models/service_structure.py +1 -1
  103. factpulse/models/signature_info.py +1 -1
  104. factpulse/models/soumettre_facture_complete_request.py +1 -1
  105. factpulse/models/soumettre_facture_complete_response.py +4 -4
  106. factpulse/models/soumettre_facture_request.py +19 -7
  107. factpulse/models/soumettre_facture_response.py +1 -1
  108. factpulse/models/statut_acquittement.py +1 -1
  109. factpulse/models/statut_celery.py +40 -0
  110. factpulse/models/statut_champ_api.py +40 -0
  111. factpulse/models/statut_facture.py +1 -1
  112. factpulse/models/statut_tache.py +7 -9
  113. factpulse/models/structure_info.py +1 -1
  114. factpulse/models/syntaxe_flux.py +1 -1
  115. factpulse/models/tauxmanuel.py +2 -2
  116. factpulse/models/type_document.py +40 -0
  117. factpulse/models/type_facture.py +1 -1
  118. factpulse/models/type_flux.py +1 -1
  119. factpulse/models/type_tva.py +1 -1
  120. factpulse/models/unite.py +1 -1
  121. factpulse/models/validation_error.py +1 -1
  122. factpulse/models/{body_rechercher_factures_fournisseur_api_v1_chorus_pro_factures_rechercher_fournisseur_post.py → validation_error_detail.py} +27 -24
  123. factpulse/models/validation_error_loc_inner.py +1 -1
  124. factpulse/rest.py +8 -3
  125. factpulse-2.0.37.dist-info/METADATA +292 -0
  126. factpulse-2.0.37.dist-info/RECORD +134 -0
  127. factpulse-2.0.37.dist-info/top_level.txt +2 -0
  128. factpulse_helpers/__init__.py +96 -0
  129. factpulse_helpers/client.py +1887 -0
  130. factpulse_helpers/exceptions.py +253 -0
  131. factpulse/api/processing_endpoints_unifis_api.py +0 -592
  132. factpulse/api/signature_lectronique_api.py +0 -1358
  133. factpulse/models/body_lister_services_structure_api_v1_chorus_pro_structures_id_structure_cpp_services_get.py +0 -102
  134. factpulse/models/body_rechercher_factures_destinataire_api_v1_chorus_pro_factures_rechercher_destinataire_post.py +0 -104
  135. factpulse/models/body_recycler_facture_api_v1_chorus_pro_factures_recycler_post.py +0 -104
  136. factpulse/models/body_telecharger_groupe_factures_api_v1_chorus_pro_factures_telecharger_groupe_post.py +0 -104
  137. factpulse/models/body_traiter_facture_recue_api_v1_chorus_pro_factures_traiter_facture_recue_post.py +0 -104
  138. factpulse/models/body_valideur_consulter_facture_api_v1_chorus_pro_factures_valideur_consulter_post.py +0 -104
  139. factpulse/models/body_valideur_traiter_facture_api_v1_chorus_pro_factures_valideur_traiter_post.py +0 -104
  140. factpulse/models/facture_enrichie_info_input.py +0 -123
  141. factpulse/models/montanthttotal.py +0 -139
  142. factpulse/models/montanttva.py +0 -139
  143. factpulse-1.0.9.dist-info/METADATA +0 -182
  144. factpulse-1.0.9.dist-info/RECORD +0 -131
  145. factpulse-1.0.9.dist-info/top_level.txt +0 -1
  146. {factpulse-1.0.9.dist-info → factpulse-2.0.37.dist-info}/WHEEL +0 -0
  147. {factpulse-1.0.9.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))