Coverage for src/henrri_connect/documents/asynchro.py: 82%

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.asynchro.AsyncDocumentsClient`` 

6 Accès asynchrone aux endpoints documents. 

7 

8Notes: 

9- Utiliser de préférence l'objet ``henrri_connect.AsyncHenrriClient`` 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 AsyncHenrriClient 

30 

31DOCUMENTS_ENDPOINT = "/v1/documents" 

32 

33 

34class AsyncDocumentsClient: 

35 """ 

36 Accès asynchrone 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: AsyncHenrriClient) -> None: 

63 self._c = client 

64 

65 async 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 = await self._c.request("GET", DOCUMENTS_ENDPOINT, params=params) 

77 return PagedListResponse[Document].model_validate(resp.json()) 

78 

79 async def add(self, document: Document) -> Document: 

80 """ 

81 Crée un nouveau document. 

82 

83 Arguments 

84 - ``document`` (Document) : Document à créer. 

85 

86 Returns 

87 - ``Document`` : Document créé. 

88 """ 

89 resp = await self._c.request( 

90 "POST", 

91 DOCUMENTS_ENDPOINT, 

92 json=document.model_dump(by_alias=True, exclude_unset=True, exclude_none=True), 

93 ) 

94 return Document.model_validate(resp.json()) 

95 

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

97 """ 

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

99 

100 Arguments 

101 - ``doc_id`` (int) : Identifiant du document. 

102 

103 Returns 

104 - ``Document`` : Document trouvé. 

105 """ 

106 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}") 

107 return Document.model_validate(resp.json()) 

108 

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

110 """ 

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

112 

113 Arguments 

114 - ``doc_id`` (int) : Identifiant du document. 

115 

116 Returns 

117 - ``Document`` : Document avec toutes ses relations incluses. 

118 """ 

119 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/with-all") 

120 return Document.model_validate(resp.json()) 

121 

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

123 """ 

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

125 

126 Arguments 

127 - ``doc_id`` (int) : Identifiant du document. 

128 

129 Returns 

130 - ``Document`` : Document avec toutes ses données incluses. 

131 """ 

132 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/all-included") 

133 data = resp.json() 

134 # L'API retourne parfois un objet enveloppé {"document": {...}, ...} 

135 # Déballer automatiquement si nécessaire pour satisfaire le modèle `Document`. 

136 if isinstance(data, dict) and "document" in data and isinstance(data["document"], dict): 

137 data = data["document"] 

138 return Document.model_validate(data) 

139 

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

141 """ 

142 Met à jour un document existant. 

143 

144 Arguments 

145 - ``doc_id`` (int) : Identifiant du document. 

146 - ``document`` (Document) : Document à mettre à jour. 

147 

148 Returns 

149 - ``Document`` : Document mis à jour. 

150 """ 

151 resp = await self._c.request( 

152 "PUT", 

153 f"{DOCUMENTS_ENDPOINT}/{doc_id}", 

154 json=document.model_dump(by_alias=True, exclude_unset=True, exclude_none=True), 

155 ) 

156 return Document.model_validate(resp.json()) 

157 

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

159 """ 

160 Supprime un document. 

161 

162 Arguments 

163 - ``doc_id`` (int) : Identifiant du document. 

164 """ 

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

166 

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

168 """ 

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

170 

171 Arguments 

172 - ``doc_id`` (int) : Identifiant du document. 

173 

174 Returns 

175 - ``TaxDetailArray`` : Détail des taxes du document. 

176 """ 

177 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/tax-details") 

178 return TaxDetailArray.model_validate(resp.json()) 

179 

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

181 """ 

182 Valide électroniquement un document. 

183 

184 Arguments 

185 - ``doc_id`` (int) : Identifiant du document. 

186 - ``request`` (ValidateDocumentRequest) : Dossier à valider. 

187 

188 Returns 

189 - ``Document`` : Document validé. 

190 """ 

191 resp = await self._c.request( 

192 "POST", 

193 f"{DOCUMENTS_ENDPOINT}/{doc_id}/validate", 

194 json=request.model_dump(by_alias=True, exclude_unset=True, exclude_none=True), 

195 ) 

196 return Document.model_validate(resp.json()) 

197 

198 async def get_pdf_url(self, doc_id: int) -> PdfUrlResponse: 

199 """ 

200 Génère une URL de téléchargement pour le PDF d'un document. 

201 

202 Arguments 

203 - ``doc_id`` (int) : Identifiant du document. 

204 

205 Returns 

206 - ``PdfUrlResponse`` : URL de téléchargement du PDF du document. 

207 """ 

208 resp = await self._c.request("POST", f"{DOCUMENTS_ENDPOINT}/{doc_id}/pdf/url") 

209 return PdfUrlResponse.model_validate(resp.json()) 

210 

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

212 """ 

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

214 

215 Arguments 

216 - ``doc_id`` (int) : Identifiant du document. 

217 

218 Returns 

219 - ``bytes`` : Octets bruts du PDF du document. 

220 """ 

221 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/pdf") 

222 return resp.content 

223 

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

225 """ 

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

227 

228 Arguments 

229 - ``doc_id`` (int) : Identifiant du document. 

230 - ``guid`` (str) : GUID du fichier PDF. 

231 

232 Returns 

233 - ``bytes`` : Octets bruts du fichier PDF. 

234 """ 

235 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/pdf/files/{guid}") 

236 return resp.content 

237 

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

239 """ 

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

241 

242 Arguments 

243 - ``doc_id`` (int) : Identifiant du document. 

244 

245 Returns 

246 - ``Document`` : Document avec les données d'affichage. 

247 """ 

248 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/display") 

249 return Document.model_validate(resp.json()) 

250 

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

252 """ 

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

254 

255 Arguments 

256 - ``doc_id`` (int) : Identifiant du document. 

257 

258 Returns 

259 - ``ListResponse[PaymentMilestone]`` : Liste des jalons de paiement du document. 

260 """ 

261 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/{doc_id}/paymentmilestones") 

262 return ListResponse[PaymentMilestone].model_validate(resp.json()) 

263 

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

265 """ 

266 Finalise un document. 

267 

268 Arguments 

269 - ``doc_id`` (int) : Identifiant du document. 

270 

271 Returns 

272 - ``Document`` : Document finalisé. 

273 """ 

274 resp = await self._c.request("POST", f"{DOCUMENTS_ENDPOINT}/{doc_id}/finalize") 

275 return Document.model_validate(resp.json()) 

276 

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

278 """ 

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

280 

281 Arguments 

282 - ``doc_id`` (int) : Identifiant du document. 

283 

284 Returns 

285 - ``Document`` : Document transformé en facture. 

286 """ 

287 resp = await self._c.request("POST", f"{DOCUMENTS_ENDPOINT}/{doc_id}/transform-to-invoice") 

288 return Document.model_validate(resp.json()) 

289 

290 async def get_next_quote_batch(self) -> object: 

291 """ 

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

293 

294 Returns 

295 - ``object`` : Numéro de lot. 

296 """ 

297 resp = await self._c.request("GET", f"{DOCUMENTS_ENDPOINT}/next-quote-batch") 

298 return resp.json() 

299 

300 async def list_with_selected_fields( 

301 self, 

302 *, 

303 page: int = 1, 

304 limit: int = 50, 

305 fields: str | None = None, 

306 **kwargs: object, 

307 ) -> PagedListResponse[Document]: 

308 """ 

309 Liste les documents avec sélection de champs. 

310 

311 Arguments 

312 - ``page`` (int) : Numéro de page (par défaut 1). 

313 - ``limit`` (int) : Nombre de documents par page (par défaut 50). 

314 - ``fields`` (str | None) : Champs à retourner (par défaut None). 

315 - ``**kwargs`` (object) : Autres paramètres. 

316 

317 Returns 

318 - ``PagedListResponse[Document]`` : Liste de documents. 

319 """ 

320 params = clean({"page": page, "limit": limit, "fields": fields, **kwargs}) 

321 resp = await self._c.request( 

322 "GET", 

323 f"{DOCUMENTS_ENDPOINT}/with-selected-fields", 

324 params=params 

325 ) 

326 return PagedListResponse[Document].model_validate(resp.json())