Coverage for src/henrri_connect/documents/synchro.py: 79%
66 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-09 19:37 +0200
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-09 19:37 +0200
1"""
2Sous-client pour les endpoints /v1/documents.
4Classes:
5- ``henrri_connect.documents.synchro.SyncDocumentsClient``
6 Accès synchrone aux endpoints documents.
8Notes:
9- Utiliser de préférence l'objet ``henrri_connect.SyncHenrriClient`` pour acceder aux endpoints.
10"""
12from __future__ import annotations
14from typing import TYPE_CHECKING
16from ..models import (
17 Document,
18 DocumentQuery,
19 ListResponse,
20 PagedListResponse,
21 PaymentMilestone,
22 PdfUrlResponse,
23 TaxDetailArray,
24 ValidateDocumentRequest,
25)
26from ..utils import clean
28if TYPE_CHECKING:
29 from ..connect import SyncHenrriClient
31DOCUMENTS_ENDPOINT = "/v1/documents"
34class SyncDocumentsClient:
35 """
36 Accès synchrone aux endpoints documents.
38 Arguments
39 - ``client`` : Objet ``henrri_connect.AsyncHenrriClient``.
41 Methods
42 - ``list_documents`` : Liste tous les documents.
43 - ``add`` : Ajoute un document.
44 - ``get`` : Accès au document par son ID.
45 - ``get_with_all`` : Accès au document par son ID avec toutes ses relations incluses.
46 - ``get_all_included`` : Accès au document par son ID avec toutes ses données incluses.
47 - ``modify`` : Modifie un document par son ID.
48 - ``delete`` : Supprime un document par son ID.
49 - ``get_tax_details`` : Accès aux taxes d'un document par son ID.
50 - ``validate`` : Valide un document.
51 - ``get_pdf_url`` : Accès au document par son ID et son GUID.
52 - ``get_pdf_bytes`` : Accès au document par son ID et son GUID.
53 - ``get_pdf_file`` : Accès au document par son ID et son GUID.
54 - ``get_display`` : Accès aux données d'affichage d'un document par son ID.
55 - ``get_payment_milestones`` : Accès aux milestones de paiement d'un document par son ID.
56 - ``finalize`` : Finalise un document.
57 - ``transform_to_invoice`` : Transforme un document (devis, bon de livraison...) en facture.
58 - ``get_next_code_batch`` : Accès au code de batch suivant.
59 - ``list_with_selected_fields`` : Liste les documents avec sélection de champs.
60 """
62 def __init__(self, client: SyncHenrriClient) -> None:
63 self._c = client
65 def list_documents(self, *, request: DocumentQuery) -> PagedListResponse[Document]:
66 """
67 Liste les documents avec pagination et filtres optionnels.
69 Arguments
70 - ``request`` (DocumentQuery) : Paramètres de recherche.
72 Returns
73 - ``PagedListResponse[Document]`` : Liste paginée de documents.
74 """
75 params = clean(request.model_dump(by_alias=True))
76 resp = self._c.request("GET", DOCUMENTS_ENDPOINT, params=params)
77 return PagedListResponse[Document].model_validate(resp.json())
79 def add(self, document: Document) -> Document:
80 """
81 Crée un nouveau document.
82 Les champs unset ou None du document sont exclus de la requête.
84 Args
85 - document (Document) : Document à créer.
87 Returns
88 - Document: Le document créé avec les données retournées par l'API.
90 Raises
91 - HTTPError: Si la requête échoue (code de statut 4xx ou 5xx).
92 """
93 resp = self._c.request(
94 "POST",
95 DOCUMENTS_ENDPOINT,
96 json=document.model_dump(by_alias=True, exclude_unset=True, exclude_none=True),
97 )
98 return Document.model_validate(resp.json())
100 def get(self, doc_id: int) -> Document:
101 """
102 Récupère un document par son identifiant.
104 Arguments
105 - doc_id (int) : Identifiant du document à reafficher.
107 Returns
108 - Document: Le document retourné par l'API.
110 Raises
111 - HTTPError: Si la requête échoue (code de statut 4xx ou 5xx).
112 """
113 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}")
114 return Document.model_validate(resp.json())
116 def get_with_all(self, doc_id: int) -> Document:
117 """
118 Récupère un document avec toutes ses relations incluses.
120 Arguments
121 - doc_id (int) : Identifiant du document à reafficher.
123 Returns
124 - Document: Le document retourné par l'API.
125 """
126 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/with-all")
127 return Document.model_validate(resp.json())
129 def get_all_included(self, doc_id: int) -> Document:
130 """
131 Récupère un document avec toutes ses données incluses.
133 Arguments
134 - doc_id (int) : Identifiant du document à reafficher.
136 Returns
137 - Document: Le document retourné par l'API.
138 """
139 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/all-included")
140 data = resp.json()
141 # L'API retourne parfois un objet enveloppé {"document": {...}, ...}
142 # Déballer automatiquement si nécessaire pour satisfaire le modèle `Document`.
143 if isinstance(data, dict) and "document" in data and isinstance(data["document"], dict):
144 data = data["document"]
145 return Document.model_validate(data)
147 def modify(self, doc_id: int, document: Document) -> Document:
148 """
149 Met à jour un document existant.
151 Arguments
152 - doc_id (int) : Identifiant du document à mettre à jour.
153 - document (Document) : Document à mettre à jour.
155 Returns
156 - Document: Le document mis à jour par l'API.
157 """
158 resp = self._c.request(
159 "PUT",
160 f"{DOCUMENTS_ENDPOINT}/{doc_id}",
161 json=document.model_dump(by_alias=True, exclude_unset=True, exclude_none=True),
162 )
163 return Document.model_validate(resp.json())
165 def delete(self, doc_id: int) -> None:
166 """
167 Supprime un document.
169 Arguments
170 - doc_id (int) : Identifiant du document à supprimer.
171 """
172 self._c.request("DELETE", f"{DOCUMENTS_ENDPOINT}/{doc_id}")
174 def get_tax_details(self, doc_id: int) -> TaxDetailArray:
175 """
176 Récupère le détail des taxes d'un document.
178 Arguments
179 - doc_id (int) : Identifiant du document.
181 Returns
182 - TaxDetailArray: Le détail des taxes retourné par l'API.
183 """
184 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/tax-details")
185 return TaxDetailArray.model_validate(resp.json())
187 def validate(self, doc_id: int, request: ValidateDocumentRequest) -> Document:
188 """
189 Valide électroniquement un document.
191 Arguments
192 - doc_id (int) : Identifiant du document à valider.
193 - request (ValidateDocumentRequest) : La requête de validation du document.
195 Returns
196 - Document: Le document validé par l'API.
197 """
198 resp = self._c.request(
199 "POST",
200 f"{DOCUMENTS_ENDPOINT}/{doc_id}/validate",
201 json=request.model_dump(by_alias=True, exclude_unset=True, exclude_none=True),
202 )
203 return Document.model_validate(resp.json())
205 def get_pdf_url(self, doc_id: int) -> PdfUrlResponse:
206 """
207 Génère une URL de téléchargement pour le PDF d'un document.
209 Arguments
210 - doc_id (int) : Identifiant du document.
212 Returns
213 - PdfUrlResponse: L'URL de téléchargement du PDF retourné par l'API.
214 """
215 resp = self._c.request("POST", f"{DOCUMENTS_ENDPOINT}/{doc_id}/pdf/url")
216 return PdfUrlResponse.model_validate(resp.json())
218 def get_pdf_bytes(self, doc_id: int) -> bytes:
219 """
220 Télécharge le PDF d'un document (retourne les octets bruts).
222 Arguments
223 - doc_id (int) : Identifiant du document.
225 Returns
226 - bytes: Les octets bruts du PDF retourné par l'API.
227 """
228 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/pdf")
229 return resp.content
231 def get_pdf_file(self, doc_id: int, guid: str) -> bytes:
232 """
233 Télécharge un fichier PDF identifié par son GUID.
235 Arguments
236 - doc_id (int) : Identifiant du document.
237 - guid (str) : GUID du fichier PDF.
239 Returns
240 - bytes: Les octets bruts du fichier PDF retourné par l'API.
241 """
242 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/pdf/files/{guid}")
243 return resp.content
245 def get_display(self, doc_id: int) -> Document:
246 """
247 Récupère les données d'affichage d'un document.
249 Arguments
250 - doc_id (int) : Identifiant du document.
252 Returns
253 - Document: Les données d'affichage du document retourné par l'API.
254 """
255 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/display")
256 return Document.model_validate(resp.json())
258 def get_payment_milestones(self, doc_id: int) -> ListResponse[PaymentMilestone]:
259 """
260 Récupère les jalons de paiement d'un document.
262 Arguments
263 - doc_id (int) : Identifiant du document.
265 Returns
266 - ListResponse[PaymentMilestone]: La liste des jalons de paiement du document retourné
267 par l'API.
268 """
269 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/paymentmilestones")
270 return ListResponse[PaymentMilestone].model_validate(resp.json())
272 def finalize(self, doc_id: int) -> Document:
273 """
274 Finalise un document.
276 Arguments
277 - doc_id (int) : Identifiant du document à finaliser.
279 Returns
280 - Document: Le document finalisé par l'API.
281 """
282 resp = self._c.request("POST", f"{DOCUMENTS_ENDPOINT}/{doc_id}/finalize")
283 return Document.model_validate(resp.json())
285 def transform_to_invoice(self, doc_id: int) -> Document:
286 """
287 Transforme un document (devis, bon de livraison…) en facture.
289 Arguments
290 - doc_id (int) : Identifiant du document à transformer en facture.
292 Returns
293 - Document: Le document transformé en facture par l'API.
294 """
295 resp = self._c.request("POST", f"{DOCUMENTS_ENDPOINT}/{doc_id}/transform-to-invoice")
296 return Document.model_validate(resp.json())
298 def get_next_quote_batch(self) -> object:
299 """
300 Récupère le prochain numéro de lot pour un devis.
302 Returns
303 - object: Le numéro de lot retourné par l'API.
304 """
305 resp = self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/next-quote-batch")
306 return resp.json()
308 def list_with_selected_fields(
309 self,
310 *,
311 page: int = 1,
312 limit: int = 50,
313 fields: str | None = None,
314 **kwargs: object,
315 ) -> PagedListResponse[Document]:
316 """
317 Liste les documents avec sélection de champs.
319 Arguments
320 - page (int) : Numéro de page.
321 - limit (int) : Nombre d'articles par page.
322 - fields (str | None) : Champs à retourner.
324 Returns
325 - PagedListResponse[Document]: La liste des documents retourné par l'API.
326 """
327 params = clean({"page": page, "limit": limit, "fields": fields, **kwargs})
328 resp = self._c.request(
329 "GET",
330 f"{DOCUMENTS_ENDPOINT}/with-selected-fields",
331 params=params
332 )
333 return PagedListResponse[Document].model_validate(resp.json())