libredte-lib-sdk 0.3.0__tar.gz → 0.5.0__tar.gz
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.
- libredte_lib_sdk-0.5.0/.claude/scheduled_tasks.lock +1 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/PKG-INFO +6 -13
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/README.rst +5 -12
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/book/__init__.py +1 -2
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/book/builder.py +6 -4
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/book/loader.py +2 -2
- libredte_lib_sdk-0.5.0/libredte_lib_sdk/billing/book/models.py +79 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/common.py +8 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/__init__.py +0 -2
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/builder.py +18 -16
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/dispatcher.py +48 -25
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/examples.py +1 -1
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/loader.py +8 -1
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/models.py +122 -49
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/renderer.py +1 -1
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/validator.py +1 -1
- libredte_lib_sdk-0.5.0/libredte_lib_sdk/billing/identifier/models.py +75 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/integration/models.py +3 -3
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/ownership_transfer/models.py +3 -3
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/trading_parties/models.py +7 -7
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/sdk.py +1 -3
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/pyproject.toml +1 -1
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/conftest.py +22 -14
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_book.py +8 -6
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_caf.py +6 -2
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_certificate_loader.py +5 -5
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_document_loader.py +9 -8
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_examples.py +8 -8
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_ownership_transfer.py +1 -2
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/test_models.py +126 -20
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/test_services.py +228 -51
- libredte_lib_sdk-0.3.0/libredte_lib_sdk/billing/book/models.py +0 -70
- libredte_lib_sdk-0.3.0/libredte_lib_sdk/billing/identifier/models.py +0 -40
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/.github/workflows/ci.yml +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/.github/workflows/docs.yml +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/.gitignore +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/.readthedocs.yaml +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/CLAUDE.md +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/LICENSE +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/Makefile +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/docs/Makefile +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/docs/conf.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/docs/index.rst +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/book/validator.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/enums.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/exchange/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/exchange/document_response.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/exchange/models.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/identifier/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/identifier/caf_faker.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/identifier/caf_loader.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/identifier/caf_validator.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/integration/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/integration/sii_dte.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/integration/sii_rcv.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/integration/sii_rtc.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/ownership_transfer/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/ownership_transfer/aec.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/trading_parties/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/trading_parties/mandatario_manager.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/client.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/exceptions.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/human_resources/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/human_resources/integration/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/human_resources/integration/previred_provider.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/human_resources/payroll/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/human_resources/payroll/calculator.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/human_resources/payroll/renderer.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/py.typed +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/system/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/system/certificate/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/system/certificate/loader.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/system/repository/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/system/repository/catalog.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/conftest.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/__init__.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_document_rendering.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_document_validation.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_exchange.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_human_resources.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_operation_not_found.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/integration/test_repository.py +0 -0
- {libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/tests/test_client.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sessionId":"64edfb1a-a3db-44cc-af25-6c7405269fdf","pid":73057,"procStart":"Mon Sep 7 03:13:55 2026","acquiredAt":1789224889985}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: libredte-lib-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: SDK Python para la API de LibreDTE Lib (facturacion electronica de Chile).
|
|
5
5
|
Project-URL: Homepage, https://www.libredte.cl
|
|
6
6
|
Project-URL: Source, https://github.com/libredte/libredte-lib-sdk-python
|
|
@@ -133,16 +133,9 @@ Uso
|
|
|
133
133
|
)
|
|
134
134
|
|
|
135
135
|
# 3. Envío al SII (arma el sobre EnvioDTE y luego lo envía).
|
|
136
|
-
emisor
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
'autorizacion_dte': {
|
|
140
|
-
'fecha_resolucion': '2014-08-22', 'numero_resolucion': 80,
|
|
141
|
-
},
|
|
142
|
-
}
|
|
143
|
-
sobre = b.document.dispatcher.create(
|
|
144
|
-
documento.xml_base64, certificate=certificate, emisor=emisor,
|
|
145
|
-
)
|
|
136
|
+
# `documento` ya trae certificate/emisor incluidos — no hace falta
|
|
137
|
+
# pasarlos aparte.
|
|
138
|
+
sobre = b.document.dispatcher.create(documento)
|
|
146
139
|
envio = b.integration.sii_dte.send(
|
|
147
140
|
sobre.xml_base64, certificate=certificate, company_rut='76192083-9',
|
|
148
141
|
)
|
|
@@ -220,7 +213,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
220
213
|
ejemplos = b.document.examples.list() # [ExampleSummary(id=..., category=..., case=...), ...]
|
|
221
214
|
ejemplo = b.document.examples.get(ejemplos[0].id)
|
|
222
215
|
documento = b.document.builder.build_signed(
|
|
223
|
-
ejemplo.
|
|
216
|
+
ejemplo.example, caf_xml=caf.xml_base64, certificate=certificate,
|
|
224
217
|
)
|
|
225
218
|
|
|
226
219
|
# Catálogos/repositorios reales (comunas, tipos de documento, etc.)
|
|
@@ -238,7 +231,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
238
231
|
# ya extraídos en una sola llamada — mismo `Certificate` que usan
|
|
239
232
|
# build_signed()/dispatcher.create(), ya con todos los datos.
|
|
240
233
|
certificate = s.certificate.loader.load(datos_del_archivo_pfx, 'contraseña')
|
|
241
|
-
print(certificate.
|
|
234
|
+
print(certificate.id, certificate.name, certificate.email)
|
|
242
235
|
|
|
243
236
|
Errores
|
|
244
237
|
-------
|
|
@@ -89,16 +89,9 @@ Uso
|
|
|
89
89
|
)
|
|
90
90
|
|
|
91
91
|
# 3. Envío al SII (arma el sobre EnvioDTE y luego lo envía).
|
|
92
|
-
emisor
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
'autorizacion_dte': {
|
|
96
|
-
'fecha_resolucion': '2014-08-22', 'numero_resolucion': 80,
|
|
97
|
-
},
|
|
98
|
-
}
|
|
99
|
-
sobre = b.document.dispatcher.create(
|
|
100
|
-
documento.xml_base64, certificate=certificate, emisor=emisor,
|
|
101
|
-
)
|
|
92
|
+
# `documento` ya trae certificate/emisor incluidos — no hace falta
|
|
93
|
+
# pasarlos aparte.
|
|
94
|
+
sobre = b.document.dispatcher.create(documento)
|
|
102
95
|
envio = b.integration.sii_dte.send(
|
|
103
96
|
sobre.xml_base64, certificate=certificate, company_rut='76192083-9',
|
|
104
97
|
)
|
|
@@ -176,7 +169,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
176
169
|
ejemplos = b.document.examples.list() # [ExampleSummary(id=..., category=..., case=...), ...]
|
|
177
170
|
ejemplo = b.document.examples.get(ejemplos[0].id)
|
|
178
171
|
documento = b.document.builder.build_signed(
|
|
179
|
-
ejemplo.
|
|
172
|
+
ejemplo.example, caf_xml=caf.xml_base64, certificate=certificate,
|
|
180
173
|
)
|
|
181
174
|
|
|
182
175
|
# Catálogos/repositorios reales (comunas, tipos de documento, etc.)
|
|
@@ -194,7 +187,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
194
187
|
# ya extraídos en una sola llamada — mismo `Certificate` que usan
|
|
195
188
|
# build_signed()/dispatcher.create(), ya con todos los datos.
|
|
196
189
|
certificate = s.certificate.loader.load(datos_del_archivo_pfx, 'contraseña')
|
|
197
|
-
print(certificate.
|
|
190
|
+
print(certificate.id, certificate.name, certificate.email)
|
|
198
191
|
|
|
199
192
|
Errores
|
|
200
193
|
-------
|
|
@@ -8,11 +8,10 @@ from __future__ import annotations
|
|
|
8
8
|
from ...client import ApiClient
|
|
9
9
|
from .builder import BookBuilderService
|
|
10
10
|
from .loader import BookLoaderService
|
|
11
|
-
from .models import
|
|
11
|
+
from .models import BookBag
|
|
12
12
|
from .validator import BookValidatorService
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
|
-
'Book',
|
|
16
15
|
'BookBag',
|
|
17
16
|
'BookBuilderService',
|
|
18
17
|
'BookComponent',
|
|
@@ -9,7 +9,7 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
from ...client import ApiClient
|
|
11
11
|
from ..trading_parties.models import Certificate
|
|
12
|
-
from .models import
|
|
12
|
+
from .models import BookBag
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class BookBuilderService:
|
|
@@ -26,17 +26,19 @@ class BookBuilderService:
|
|
|
26
26
|
bag: dict[str, Any],
|
|
27
27
|
*,
|
|
28
28
|
certificate: Certificate,
|
|
29
|
-
) ->
|
|
29
|
+
) -> BookBag:
|
|
30
30
|
"""
|
|
31
31
|
Construye el libro descrito por `bag`.
|
|
32
32
|
|
|
33
33
|
`bag` trae `tipo` (`'libro_ventas'`, `'libro_compras'`,
|
|
34
34
|
`'libro_boletas'`, `'libro_guias'`, `'resumen_ventas_diarias'`,
|
|
35
35
|
sin tipar como enum cerrado), `caratula`, `detalle` y `emisor`,
|
|
36
|
-
tal cual el formato SII.
|
|
36
|
+
tal cual el formato SII. A diferencia de `BookLoaderService
|
|
37
|
+
.load()`, acá `BookBag.book` viene poblado (el libro construido
|
|
38
|
+
y firmado, con su `xml`).
|
|
37
39
|
"""
|
|
38
40
|
data = self._client.call(
|
|
39
41
|
self._BUILD_OPERATION,
|
|
40
42
|
bag={**bag, 'certificate': certificate.to_payload()},
|
|
41
43
|
)
|
|
42
|
-
return
|
|
44
|
+
return BookBag.from_api(data)
|
|
@@ -31,8 +31,8 @@ class BookLoaderService:
|
|
|
31
31
|
Normaliza `caratula`/`detalle` de `bag` en un `BookBag`.
|
|
32
32
|
|
|
33
33
|
`bag` trae `tipo`/`caratula`/`detalle`/`emisor`, tal cual el
|
|
34
|
-
formato SII. `BookBag.
|
|
35
|
-
el libro); `BookBag.
|
|
34
|
+
formato SII. `BookBag.book` siempre viene `None` (no construye
|
|
35
|
+
el libro); `BookBag.book_auth` viene poblado solo si
|
|
36
36
|
`bag['emisor']` incluye `autorizacion_dte`.
|
|
37
37
|
"""
|
|
38
38
|
data = self._client.call(
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Copyright (C) 2026 LibreDTE <https://www.libredte.cl>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""DTO del componente `billing.book`."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, cast
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class BookBag:
|
|
15
|
+
"""
|
|
16
|
+
Bolsa de un libro tributario (`book.builder::build`/`book.loader::load`).
|
|
17
|
+
|
|
18
|
+
Ambas operaciones devuelven esta misma forma — la diferencia es qué
|
|
19
|
+
tan lejos llegó el procesamiento: `loader.load()` solo normaliza
|
|
20
|
+
`caratula`/`detalle` a partir de los datos de entrada (`book`
|
|
21
|
+
siempre viene `None`, sin importar el input); `builder.build()` sí
|
|
22
|
+
construye y firma el libro, por lo que `book` viene poblado (el
|
|
23
|
+
libro completo — genérico entre los 5 tipos, ej.
|
|
24
|
+
`libro_ventas`/`libro_compras` traen `{'LibroCompraVenta': {...}}`
|
|
25
|
+
junto a su `xml` en base64 — no se tipa más allá de `dict` porque
|
|
26
|
+
la clave raíz varía según el tipo). `book_auth` (la autorización
|
|
27
|
+
del emisor) solo viene poblada si esa autorización se incluyó junto
|
|
28
|
+
con los datos del emisor en la solicitud; si no, viene `None`.
|
|
29
|
+
|
|
30
|
+
`xml_base64`/`xml`/`xml_bytes` dan acceso directo al XML del libro
|
|
31
|
+
construido — solo válidos cuando `book` no es `None` (ver
|
|
32
|
+
`is_construido`); listos para encadenar a `BookValidatorService`.
|
|
33
|
+
No usa `XmlPayloadMixin`: acá `xml_base64` es una `@property` (el
|
|
34
|
+
libro puede no estar construido), no un campo simple.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
book: dict[str, Any] | None
|
|
38
|
+
book_type: dict[str, Any]
|
|
39
|
+
book_auth: dict[str, Any] | None
|
|
40
|
+
caratula: dict[str, Any]
|
|
41
|
+
detalle: list[dict[str, Any]]
|
|
42
|
+
raw: dict[str, Any]
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def is_construido(self) -> bool:
|
|
46
|
+
"""Si `book` viene poblado (`builder.build()`, no `loader.load()`)."""
|
|
47
|
+
return self.book is not None
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def xml_base64(self) -> str:
|
|
51
|
+
"""XML del libro construido, en base64 — ver `BookValidatorService`."""
|
|
52
|
+
if self.book is None:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
'Este BookBag no tiene un libro construido — viene de '
|
|
55
|
+
'`loader.load()`, no de `builder.build()`.',
|
|
56
|
+
)
|
|
57
|
+
return cast('str', self.book['xml'])
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def xml_bytes(self) -> bytes:
|
|
61
|
+
"""XML del libro construido, decodificado desde base64."""
|
|
62
|
+
return base64.b64decode(self.xml_base64)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def xml(self) -> str:
|
|
66
|
+
"""XML del libro construido, como texto (ISO-8859-1)."""
|
|
67
|
+
return self.xml_bytes.decode('iso-8859-1')
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_api(cls, data: dict[str, Any]) -> BookBag:
|
|
71
|
+
"""Construye un `BookBag` desde el `data` que devuelve la API."""
|
|
72
|
+
return cls(
|
|
73
|
+
book=data.get('book'),
|
|
74
|
+
book_type=data['book_type'],
|
|
75
|
+
book_auth=data.get('book_auth'),
|
|
76
|
+
caratula=data.get('caratula') or {},
|
|
77
|
+
detalle=data.get('detalle') or [],
|
|
78
|
+
raw=data,
|
|
79
|
+
)
|
|
@@ -15,6 +15,14 @@ class XmlPayloadMixin:
|
|
|
15
15
|
Espera un atributo `xml_base64: str` en la clase concreta (`Document`,
|
|
16
16
|
`DocumentEnvelope`, `Caf`, ...) y expone `xml_bytes`/`xml` sobre él,
|
|
17
17
|
para no repetir esta misma conversión en cada DTO.
|
|
18
|
+
|
|
19
|
+
Solo para clases donde `xml_base64` es un campo real del dataclass
|
|
20
|
+
(siempre presente). `BookBag`/`DocumentBag` NO lo usan — su
|
|
21
|
+
`xml_base64` es una `@property` que puede lanzar (el XML puede no
|
|
22
|
+
existir aún, ej. un borrador sin construir) y un `@property` no
|
|
23
|
+
puede sobrescribir a un atributo simple heredado sin romper el
|
|
24
|
+
orden de campos del dataclass (`xml_base64` seguido de un campo
|
|
25
|
+
obligatorio) — definen `xml_bytes`/`xml` por su cuenta.
|
|
18
26
|
"""
|
|
19
27
|
|
|
20
28
|
__slots__ = ()
|
{libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/__init__.py
RENAMED
|
@@ -11,7 +11,6 @@ from .dispatcher import DocumentDispatcherService
|
|
|
11
11
|
from .examples import DocumentExamplesService
|
|
12
12
|
from .loader import DocumentLoaderService
|
|
13
13
|
from .models import (
|
|
14
|
-
Document,
|
|
15
14
|
DocumentBag,
|
|
16
15
|
DocumentEnvelope,
|
|
17
16
|
Example,
|
|
@@ -23,7 +22,6 @@ from .renderer import DocumentRendererService
|
|
|
23
22
|
from .validator import DocumentValidatorService
|
|
24
23
|
|
|
25
24
|
__all__ = [
|
|
26
|
-
'Document',
|
|
27
25
|
'DocumentBag',
|
|
28
26
|
'DocumentBuilderService',
|
|
29
27
|
'DocumentComponent',
|
{libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/builder.py
RENAMED
|
@@ -9,7 +9,7 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
from ...client import ApiClient
|
|
11
11
|
from ..trading_parties.models import Certificate
|
|
12
|
-
from .models import
|
|
12
|
+
from .models import DocumentBag
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class DocumentBuilderService:
|
|
@@ -33,27 +33,27 @@ class DocumentBuilderService:
|
|
|
33
33
|
input_data: str | dict[str, Any],
|
|
34
34
|
*,
|
|
35
35
|
options: dict[str, Any] | None = None,
|
|
36
|
-
) ->
|
|
36
|
+
) -> DocumentBag:
|
|
37
37
|
"""
|
|
38
38
|
Emite el borrador de un DTE a partir de datos de entrada.
|
|
39
39
|
|
|
40
40
|
`input_data` es el `Encabezado`/`Detalle` (y demás nodos) del
|
|
41
|
-
formato DTE del SII como `dict
|
|
42
|
-
|
|
43
|
-
quien llama, típicamente porque lleva el correlativo) — o datos
|
|
44
|
-
en otro formato (XML/YAML, un formulario, etc.) junto con
|
|
41
|
+
formato DTE del SII como `dict` — o datos en otro formato
|
|
42
|
+
(XML/YAML, un formulario, etc.) junto con
|
|
45
43
|
`options={'parser': {'strategy': '<estrategia>'}}` para indicar
|
|
46
44
|
cómo parsearlos. Sin `options`, la API asume que `input_data` ya
|
|
47
|
-
viene en el formato DTE del SII (estrategia `default.json`).
|
|
48
|
-
|
|
49
|
-
(`
|
|
45
|
+
viene en el formato DTE del SII (estrategia `default.json`).
|
|
46
|
+
`Encabezado.IdDoc.Folio` es opcional acá — un borrador sin folio
|
|
47
|
+
no falla (queda con folio `0` en `DocumentBag.document_id`, sin
|
|
48
|
+
nodo `Folio` en el XML). Sin CAF ni certificado, el resultado no
|
|
49
|
+
queda timbrado (`DocumentBag.is_timbrado` es `False`).
|
|
50
50
|
"""
|
|
51
51
|
bag: dict[str, Any] = {'inputData': input_data}
|
|
52
52
|
if options is not None:
|
|
53
53
|
bag['options'] = options
|
|
54
54
|
|
|
55
55
|
data = self._client.call(self._BUILD_OPERATION, bag=bag)
|
|
56
|
-
return
|
|
56
|
+
return DocumentBag.from_api(data)
|
|
57
57
|
|
|
58
58
|
def build_signed(
|
|
59
59
|
self,
|
|
@@ -62,14 +62,16 @@ class DocumentBuilderService:
|
|
|
62
62
|
options: dict[str, Any] | None = None,
|
|
63
63
|
caf_xml: str,
|
|
64
64
|
certificate: Certificate,
|
|
65
|
-
) ->
|
|
65
|
+
) -> DocumentBag:
|
|
66
66
|
"""
|
|
67
67
|
Genera el DTE real, timbrado y firmado.
|
|
68
68
|
|
|
69
|
-
`input_data`/`options` funcionan igual que en `build_draft()
|
|
70
|
-
|
|
71
|
-
el
|
|
72
|
-
|
|
69
|
+
`input_data`/`options` funcionan igual que en `build_draft()` —
|
|
70
|
+
acá sí se necesita un `Encabezado.IdDoc.Folio` real, cubierto
|
|
71
|
+
por `caf_xml` (el SDK no asigna folios: eso lo decide quien
|
|
72
|
+
llama, típicamente porque lleva el correlativo). Requiere un CAF
|
|
73
|
+
real (XML tal como lo entrega el SII) y el certificado digital
|
|
74
|
+
del emisor. Para pruebas, ambos se pueden generar con
|
|
73
75
|
`IdentifierComponent.caf_faker` y
|
|
74
76
|
`TradingPartiesComponent.mandatario_manager`.
|
|
75
77
|
"""
|
|
@@ -80,4 +82,4 @@ class DocumentBuilderService:
|
|
|
80
82
|
bag['certificate'] = certificate.to_payload()
|
|
81
83
|
|
|
82
84
|
data = self._client.call(self._BUILD_OPERATION, bag=bag)
|
|
83
|
-
return
|
|
85
|
+
return DocumentBag.from_api(data)
|
{libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/dispatcher.py
RENAMED
|
@@ -5,17 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from collections.abc import Sequence
|
|
8
9
|
from typing import Any, cast
|
|
9
10
|
|
|
10
11
|
from ...client import ApiClient
|
|
11
|
-
from
|
|
12
|
-
from .models import DocumentEnvelope
|
|
12
|
+
from .models import DocumentBag, DocumentEnvelope
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class DocumentDispatcherService:
|
|
16
16
|
"""Arma y valida el sobre `EnvioDTE` (`billing.document.dispatcher`)."""
|
|
17
17
|
|
|
18
18
|
_CREATE_OPERATION = 'billing.document.dispatcher::create'
|
|
19
|
+
_CREATE_MANY_OPERATION = 'billing.document.dispatcher::createMany'
|
|
19
20
|
_LOAD_XML_OPERATION = 'billing.document.dispatcher::loadXml'
|
|
20
21
|
_VALIDATE_OPERATION = 'billing.document.dispatcher::validate'
|
|
21
22
|
_VALIDATE_SCHEMA_OPERATION = 'billing.document.dispatcher::validateSchema'
|
|
@@ -27,42 +28,64 @@ class DocumentDispatcherService:
|
|
|
27
28
|
"""Guarda el `ApiClient` compartido usado para llamar a la API."""
|
|
28
29
|
self._client = client
|
|
29
30
|
|
|
30
|
-
def create(
|
|
31
|
-
self,
|
|
32
|
-
document_xml_base64: str,
|
|
33
|
-
*,
|
|
34
|
-
certificate: Certificate,
|
|
35
|
-
emisor: dict[str, Any],
|
|
36
|
-
) -> DocumentEnvelope:
|
|
31
|
+
def create(self, bag: DocumentBag) -> DocumentEnvelope:
|
|
37
32
|
"""
|
|
38
33
|
Envuelve y firma un documento ya timbrado en un sobre `EnvioDTE`.
|
|
39
34
|
|
|
40
|
-
`
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`emisor` es un `dict` con `rut`/`razon_social`/
|
|
46
|
-
`autorizacion_dte` (esta última con `fecha_resolucion`/
|
|
47
|
-
`numero_resolucion`), tal como lo espera la API.
|
|
35
|
+
`bag` es el `DocumentBag` ya timbrado y firmado que devuelve
|
|
36
|
+
`DocumentBuilderService.build_signed()` — trae `certificate`/
|
|
37
|
+
`emisor` incluidos, no hace falta pasarlos aparte. El sobre
|
|
38
|
+
resultante es lo que se envía al SII (`SiiDteService.send`).
|
|
48
39
|
"""
|
|
49
40
|
data = self._client.call(
|
|
50
41
|
self._CREATE_OPERATION,
|
|
51
|
-
bag=
|
|
52
|
-
'xmlDocument': document_xml_base64,
|
|
53
|
-
'certificate': certificate.to_payload(),
|
|
54
|
-
'emisor': emisor,
|
|
55
|
-
},
|
|
42
|
+
bag=self._bag_payload(bag),
|
|
56
43
|
)
|
|
57
44
|
return DocumentEnvelope.from_api(data)
|
|
58
45
|
|
|
46
|
+
def create_many(self, bags: Sequence[DocumentBag]) -> DocumentEnvelope:
|
|
47
|
+
"""
|
|
48
|
+
Envuelve y firma 2+ documentos ya timbrados en un sobre `EnvioDTE`.
|
|
49
|
+
|
|
50
|
+
Igual que `create()`, pero para un sobre que agrupa más de un
|
|
51
|
+
documento — cada `DocumentBag` trae su propio `certificate`/
|
|
52
|
+
`emisor`. El sobre resultante queda firmado con el certificado
|
|
53
|
+
del PRIMER `DocumentBag` (el sobre en sí también se firma, no
|
|
54
|
+
solo cada documento).
|
|
55
|
+
"""
|
|
56
|
+
data = self._client.call(
|
|
57
|
+
self._CREATE_MANY_OPERATION,
|
|
58
|
+
bags=[self._bag_payload(bag) for bag in bags],
|
|
59
|
+
)
|
|
60
|
+
return DocumentEnvelope.from_api(data)
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _bag_payload(bag: DocumentBag) -> dict[str, Any]:
|
|
64
|
+
if bag.certificate is None:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
'Este DocumentBag no tiene certificado — ¿se firmó con '
|
|
67
|
+
'`builder.build_signed()`?',
|
|
68
|
+
)
|
|
69
|
+
if bag.emisor is None:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
'Este DocumentBag no tiene emisor — ¿se construyó con '
|
|
72
|
+
'`builder.build_draft/build_signed()` o se cargó con '
|
|
73
|
+
'`loader.load_xml()`?',
|
|
74
|
+
)
|
|
75
|
+
return {
|
|
76
|
+
'xmlDocument': bag.xml_base64,
|
|
77
|
+
'certificate': bag.certificate.to_payload(),
|
|
78
|
+
'emisor': bag.emisor,
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
def load_xml(self, xml_base64: str) -> DocumentEnvelope:
|
|
60
82
|
"""
|
|
61
83
|
Carga un sobre `EnvioDTE` ya existente desde su XML (base64).
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
El `DocumentEnvelope` resultante trae en `documents` una bolsa
|
|
86
|
+
por cada documento tributario que el sobre agrupaba — uno o
|
|
87
|
+
más — y en `caratula` los datos de la carátula ya presente en
|
|
88
|
+
ese XML.
|
|
66
89
|
"""
|
|
67
90
|
data = self._client.call(self._LOAD_XML_OPERATION, xml=xml_base64)
|
|
68
91
|
return DocumentEnvelope.from_api(data)
|
{libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/examples.py
RENAMED
|
@@ -17,7 +17,7 @@ class DocumentExamplesService:
|
|
|
17
17
|
de `libredte-lib-core` (`tests/fixtures/yaml/documentos_ok/`) — uno
|
|
18
18
|
por variante de negocio real (descuentos, impuesto adicional, pago a
|
|
19
19
|
crédito, exportación, etc.), no datos inventados por el SDK.
|
|
20
|
-
`Example.
|
|
20
|
+
`Example.example` de `get()` se le pasa tal cual a
|
|
21
21
|
`DocumentBuilderService.build_draft()`/`.build_signed()`, típicamente
|
|
22
22
|
reemplazando `Encabezado.IdDoc.Folio` y `Encabezado.Receptor` por los
|
|
23
23
|
propios de quien usa el SDK antes de construir el documento.
|
{libredte_lib_sdk-0.3.0 → libredte_lib_sdk-0.5.0}/libredte_lib_sdk/billing/document/loader.py
RENAMED
|
@@ -26,6 +26,13 @@ class DocumentLoaderService:
|
|
|
26
26
|
self._client = client
|
|
27
27
|
|
|
28
28
|
def load_xml(self, xml_base64: str) -> DocumentBag:
|
|
29
|
-
"""
|
|
29
|
+
"""
|
|
30
|
+
Carga y normaliza el documento cuyo XML (base64) es `xml_base64`.
|
|
31
|
+
|
|
32
|
+
Devuelve la misma `DocumentBag` que `DocumentBuilderService` —
|
|
33
|
+
`document_id`/`xml_base64` reconstruyen lo que ya se le pasó
|
|
34
|
+
acá, `document`/`document_type`/`document_stamp_base64`/etc.
|
|
35
|
+
son el valor agregado real: los datos ya normalizados.
|
|
36
|
+
"""
|
|
30
37
|
data = self._client.call(self._LOAD_XML_OPERATION, xml=xml_base64)
|
|
31
38
|
return DocumentBag.from_api(data)
|