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

1""" 

2Sous-client pour les endpoints /v1/documents. 

3 

4Classes: 

5- ``henrri_connect.documents.synchro.SyncDocumentsClient`` 

6 Accès synchrone aux endpoints documents. 

7 

8Notes: 

9- Utiliser de préférence l'objet ``henrri_connect.SyncHenrriClient`` pour acceder aux endpoints. 

10""" 

11 

12from __future__ import annotations 

13 

14from typing import TYPE_CHECKING 

15 

16from ..models import ( 

17 Document, 

18 DocumentQuery, 

19 ListResponse, 

20 PagedListResponse, 

21 PaymentMilestone, 

22 PdfUrlResponse, 

23 TaxDetailArray, 

24 ValidateDocumentRequest, 

25) 

26from ..utils import clean 

27 

28if TYPE_CHECKING: 

29 from ..connect import SyncHenrriClient 

30 

31DOCUMENTS_ENDPOINT = "/v1/documents" 

32 

33 

34class SyncDocumentsClient: 

35 """ 

36 Accès synchrone aux endpoints documents. 

37 

38 Arguments 

39 - ``client`` : Objet ``henrri_connect.AsyncHenrriClient``. 

40 

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 """ 

61 

62 def __init__(self, client: SyncHenrriClient) -> None: 

63 self._c = client 

64 

65 def list_documents(self, *, request: DocumentQuery) -> PagedListResponse[Document]: 

66 """ 

67 Liste les documents avec pagination et filtres optionnels. 

68 

69 Arguments 

70 - ``request`` (DocumentQuery) : Paramètres de recherche. 

71 

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()) 

78 

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. 

83 

84 Args 

85 - document (Document) : Document à créer. 

86 

87 Returns 

88 - Document: Le document créé avec les données retournées par l'API. 

89 

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()) 

99 

100 def get(self, doc_id: int) -> Document: 

101 """ 

102 Récupère un document par son identifiant. 

103 

104 Arguments 

105 - doc_id (int) : Identifiant du document à reafficher. 

106 

107 Returns 

108 - Document: Le document retourné par l'API. 

109 

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()) 

115 

116 def get_with_all(self, doc_id: int) -> Document: 

117 """ 

118 Récupère un document avec toutes ses relations incluses. 

119 

120 Arguments 

121 - doc_id (int) : Identifiant du document à reafficher. 

122 

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()) 

128 

129 def get_all_included(self, doc_id: int) -> Document: 

130 """ 

131 Récupère un document avec toutes ses données incluses. 

132 

133 Arguments 

134 - doc_id (int) : Identifiant du document à reafficher. 

135 

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) 

146 

147 def modify(self, doc_id: int, document: Document) -> Document: 

148 """ 

149 Met à jour un document existant. 

150 

151 Arguments 

152 - doc_id (int) : Identifiant du document à mettre à jour. 

153 - document (Document) : Document à mettre à jour. 

154 

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()) 

164 

165 def delete(self, doc_id: int) -> None: 

166 """ 

167 Supprime un document. 

168 

169 Arguments 

170 - doc_id (int) : Identifiant du document à supprimer. 

171 """ 

172 self._c.request("DELETE", f"{DOCUMENTS_ENDPOINT}/{doc_id}") 

173 

174 def get_tax_details(self, doc_id: int) -> TaxDetailArray: 

175 """ 

176 Récupère le détail des taxes d'un document. 

177 

178 Arguments 

179 - doc_id (int) : Identifiant du document. 

180 

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()) 

186 

187 def validate(self, doc_id: int, request: ValidateDocumentRequest) -> Document: 

188 """ 

189 Valide électroniquement un document. 

190 

191 Arguments 

192 - doc_id (int) : Identifiant du document à valider. 

193 - request (ValidateDocumentRequest) : La requête de validation du document. 

194 

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()) 

204 

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. 

208 

209 Arguments 

210 - doc_id (int) : Identifiant du document. 

211 

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()) 

217 

218 def get_pdf_bytes(self, doc_id: int) -> bytes: 

219 """ 

220 Télécharge le PDF d'un document (retourne les octets bruts). 

221 

222 Arguments 

223 - doc_id (int) : Identifiant du document. 

224 

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 

230 

231 def get_pdf_file(self, doc_id: int, guid: str) -> bytes: 

232 """ 

233 Télécharge un fichier PDF identifié par son GUID. 

234 

235 Arguments 

236 - doc_id (int) : Identifiant du document. 

237 - guid (str) : GUID du fichier PDF. 

238 

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 

244 

245 def get_display(self, doc_id: int) -> Document: 

246 """ 

247 Récupère les données d'affichage d'un document. 

248 

249 Arguments 

250 - doc_id (int) : Identifiant du document. 

251 

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()) 

257 

258 def get_payment_milestones(self, doc_id: int) -> ListResponse[PaymentMilestone]: 

259 """ 

260 Récupère les jalons de paiement d'un document. 

261 

262 Arguments 

263 - doc_id (int) : Identifiant du document. 

264 

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()) 

271 

272 def finalize(self, doc_id: int) -> Document: 

273 """ 

274 Finalise un document. 

275 

276 Arguments 

277 - doc_id (int) : Identifiant du document à finaliser. 

278 

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()) 

284 

285 def transform_to_invoice(self, doc_id: int) -> Document: 

286 """ 

287 Transforme un document (devis, bon de livraison…) en facture. 

288 

289 Arguments 

290 - doc_id (int) : Identifiant du document à transformer en facture. 

291 

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()) 

297 

298 def get_next_quote_batch(self) -> object: 

299 """ 

300 Récupère le prochain numéro de lot pour un devis. 

301 

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() 

307 

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. 

318 

319 Arguments 

320 - page (int) : Numéro de page. 

321 - limit (int) : Nombre d'articles par page. 

322 - fields (str | None) : Champs à retourner. 

323 

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())