libredte-lib-sdk 0.2.0__tar.gz → 0.4.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.4.0/.claude/scheduled_tasks.lock +1 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/PKG-INFO +6 -6
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/README.rst +5 -5
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/loader.py +2 -2
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/models.py +9 -9
- libredte_lib_sdk-0.4.0/libredte_lib_sdk/billing/document/builder.py +83 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/examples.py +1 -1
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/models.py +13 -13
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/renderer.py +16 -7
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/models.py +3 -3
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/ownership_transfer/models.py +3 -3
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/trading_parties/models.py +7 -7
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/sdk.py +2 -2
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/pyproject.toml +1 -1
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/conftest.py +5 -5
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_book.py +1 -1
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_certificate_loader.py +5 -5
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_examples.py +8 -8
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_exchange.py +2 -2
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_ownership_transfer.py +1 -1
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/test_client.py +1 -1
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/test_models.py +2 -2
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/test_services.py +68 -20
- libredte_lib_sdk-0.2.0/libredte_lib_sdk/billing/document/builder.py +0 -71
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.github/workflows/ci.yml +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.github/workflows/docs.yml +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.gitignore +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.readthedocs.yaml +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/CLAUDE.md +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/LICENSE +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/Makefile +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/docs/Makefile +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/docs/conf.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/docs/index.rst +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/builder.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/validator.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/common.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/dispatcher.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/loader.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/validator.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/enums.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/document_response.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/models.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_faker.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_loader.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_validator.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/models.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_dte.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_rcv.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_rtc.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/ownership_transfer/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/ownership_transfer/aec.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/trading_parties/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/trading_parties/mandatario_manager.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/client.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/exceptions.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/integration/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/integration/previred_provider.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/payroll/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/payroll/calculator.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/payroll/renderer.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/py.typed +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/certificate/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/certificate/loader.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/repository/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/repository/catalog.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/conftest.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/__init__.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_caf.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_loader.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_rendering.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_validation.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_human_resources.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_operation_not_found.py +0 -0
- {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_repository.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.4.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
|
|
@@ -80,7 +80,7 @@ Uso
|
|
|
80
80
|
sdk = LibreDTE() # base_url y api_token también vía env vars, ver abajo
|
|
81
81
|
b = sdk.billing
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
input_data = {
|
|
84
84
|
'Encabezado': {
|
|
85
85
|
'IdDoc': {
|
|
86
86
|
'TipoDTE': 33,
|
|
@@ -125,11 +125,11 @@ Uso
|
|
|
125
125
|
# b.identifier.caf_validator.validate(caf.xml_base64)
|
|
126
126
|
|
|
127
127
|
# 1. Borrador.
|
|
128
|
-
borrador = b.document.builder.build_draft(
|
|
128
|
+
borrador = b.document.builder.build_draft(input_data)
|
|
129
129
|
|
|
130
130
|
# 2. Documento real, timbrado y firmado.
|
|
131
131
|
documento = b.document.builder.build_signed(
|
|
132
|
-
|
|
132
|
+
input_data, caf_xml=caf.xml_base64, certificate=certificate,
|
|
133
133
|
)
|
|
134
134
|
|
|
135
135
|
# 3. Envío al SII (arma el sobre EnvioDTE y luego lo envía).
|
|
@@ -220,7 +220,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
220
220
|
ejemplos = b.document.examples.list() # [ExampleSummary(id=..., category=..., case=...), ...]
|
|
221
221
|
ejemplo = b.document.examples.get(ejemplos[0].id)
|
|
222
222
|
documento = b.document.builder.build_signed(
|
|
223
|
-
ejemplo.
|
|
223
|
+
ejemplo.example, caf_xml=caf.xml_base64, certificate=certificate,
|
|
224
224
|
)
|
|
225
225
|
|
|
226
226
|
# Catálogos/repositorios reales (comunas, tipos de documento, etc.)
|
|
@@ -238,7 +238,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
238
238
|
# ya extraídos en una sola llamada — mismo `Certificate` que usan
|
|
239
239
|
# build_signed()/dispatcher.create(), ya con todos los datos.
|
|
240
240
|
certificate = s.certificate.loader.load(datos_del_archivo_pfx, 'contraseña')
|
|
241
|
-
print(certificate.
|
|
241
|
+
print(certificate.id, certificate.name, certificate.email)
|
|
242
242
|
|
|
243
243
|
Errores
|
|
244
244
|
-------
|
|
@@ -36,7 +36,7 @@ Uso
|
|
|
36
36
|
sdk = LibreDTE() # base_url y api_token también vía env vars, ver abajo
|
|
37
37
|
b = sdk.billing
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
input_data = {
|
|
40
40
|
'Encabezado': {
|
|
41
41
|
'IdDoc': {
|
|
42
42
|
'TipoDTE': 33,
|
|
@@ -81,11 +81,11 @@ Uso
|
|
|
81
81
|
# b.identifier.caf_validator.validate(caf.xml_base64)
|
|
82
82
|
|
|
83
83
|
# 1. Borrador.
|
|
84
|
-
borrador = b.document.builder.build_draft(
|
|
84
|
+
borrador = b.document.builder.build_draft(input_data)
|
|
85
85
|
|
|
86
86
|
# 2. Documento real, timbrado y firmado.
|
|
87
87
|
documento = b.document.builder.build_signed(
|
|
88
|
-
|
|
88
|
+
input_data, caf_xml=caf.xml_base64, certificate=certificate,
|
|
89
89
|
)
|
|
90
90
|
|
|
91
91
|
# 3. Envío al SII (arma el sobre EnvioDTE y luego lo envía).
|
|
@@ -176,7 +176,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
176
176
|
ejemplos = b.document.examples.list() # [ExampleSummary(id=..., category=..., case=...), ...]
|
|
177
177
|
ejemplo = b.document.examples.get(ejemplos[0].id)
|
|
178
178
|
documento = b.document.builder.build_signed(
|
|
179
|
-
ejemplo.
|
|
179
|
+
ejemplo.example, caf_xml=caf.xml_base64, certificate=certificate,
|
|
180
180
|
)
|
|
181
181
|
|
|
182
182
|
# Catálogos/repositorios reales (comunas, tipos de documento, etc.)
|
|
@@ -194,7 +194,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
|
|
|
194
194
|
# ya extraídos en una sola llamada — mismo `Certificate` que usan
|
|
195
195
|
# build_signed()/dispatcher.create(), ya con todos los datos.
|
|
196
196
|
certificate = s.certificate.loader.load(datos_del_archivo_pfx, 'contraseña')
|
|
197
|
-
print(certificate.
|
|
197
|
+
print(certificate.id, certificate.name, certificate.email)
|
|
198
198
|
|
|
199
199
|
Errores
|
|
200
200
|
-------
|
|
@@ -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(
|
|
@@ -43,16 +43,16 @@ class BookBag:
|
|
|
43
43
|
Esta operación solo normaliza `caratula`/`detalle` a partir de los
|
|
44
44
|
datos de entrada — no construye el libro ni genera su documento
|
|
45
45
|
firmado (eso lo hace `BookBuilderService.build`), por lo que
|
|
46
|
-
`
|
|
47
|
-
importar los datos de entrada recibidos. `
|
|
48
|
-
del emisor del libro) solo viene poblada si esa
|
|
49
|
-
incluyó junto con los datos del emisor en la
|
|
50
|
-
`None`.
|
|
46
|
+
`book` (el libro ya construido) siempre viene `None` acá, sin
|
|
47
|
+
importar los datos de entrada recibidos. `book_auth` (la
|
|
48
|
+
autorización del emisor del libro) solo viene poblada si esa
|
|
49
|
+
autorización se incluyó junto con los datos del emisor en la
|
|
50
|
+
solicitud; si no, viene `None`.
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
book: dict[str, Any] | None
|
|
54
54
|
book_type: dict[str, Any]
|
|
55
|
-
|
|
55
|
+
book_auth: dict[str, Any] | None
|
|
56
56
|
caratula: dict[str, Any]
|
|
57
57
|
detalle: list[dict[str, Any]]
|
|
58
58
|
raw: dict[str, Any]
|
|
@@ -61,9 +61,9 @@ class BookBag:
|
|
|
61
61
|
def from_api(cls, data: dict[str, Any]) -> BookBag:
|
|
62
62
|
"""Construye un `BookBag` desde el `data` que devuelve la API."""
|
|
63
63
|
return cls(
|
|
64
|
-
|
|
64
|
+
book=data.get('book'),
|
|
65
65
|
book_type=data['book_type'],
|
|
66
|
-
|
|
66
|
+
book_auth=data.get('book_auth'),
|
|
67
67
|
caratula=data.get('caratula') or {},
|
|
68
68
|
detalle=data.get('detalle') or [],
|
|
69
69
|
raw=data,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Copyright (C) 2026 LibreDTE <https://www.libredte.cl>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""Servicio para `billing.document.builder`."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ...client import ApiClient
|
|
11
|
+
from ..trading_parties.models import Certificate
|
|
12
|
+
from .models import Document
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DocumentBuilderService:
|
|
16
|
+
"""
|
|
17
|
+
Construye documentos tributarios (`billing.document.builder`).
|
|
18
|
+
|
|
19
|
+
El mismo worker de la API sirve tanto para un borrador como para el
|
|
20
|
+
documento timbrado y firmado, según qué datos se le pasen — acá se
|
|
21
|
+
separa en dos métodos explícitos para que la intención de cada
|
|
22
|
+
llamada quede clara en el código que la usa.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
_BUILD_OPERATION = 'billing.document.builder::build'
|
|
26
|
+
|
|
27
|
+
def __init__(self, client: ApiClient) -> None:
|
|
28
|
+
"""Guarda el `ApiClient` compartido usado para llamar a la API."""
|
|
29
|
+
self._client = client
|
|
30
|
+
|
|
31
|
+
def build_draft(
|
|
32
|
+
self,
|
|
33
|
+
input_data: str | dict[str, Any],
|
|
34
|
+
*,
|
|
35
|
+
options: dict[str, Any] | None = None,
|
|
36
|
+
) -> Document:
|
|
37
|
+
"""
|
|
38
|
+
Emite el borrador de un DTE a partir de datos de entrada.
|
|
39
|
+
|
|
40
|
+
`input_data` es el `Encabezado`/`Detalle` (y demás nodos) del
|
|
41
|
+
formato DTE del SII como `dict`, incluyendo
|
|
42
|
+
`Encabezado.IdDoc.Folio` (el SDK no asigna folios: eso lo decide
|
|
43
|
+
quien llama, típicamente porque lleva el correlativo) — o datos
|
|
44
|
+
en otro formato (XML/YAML, un formulario, etc.) junto con
|
|
45
|
+
`options={'parser': {'strategy': '<estrategia>'}}` para indicar
|
|
46
|
+
cómo parsearlos. Sin `options`, la API asume que `input_data` ya
|
|
47
|
+
viene en el formato DTE del SII (estrategia `default.json`). Sin
|
|
48
|
+
CAF ni certificado, el resultado no queda timbrado
|
|
49
|
+
(`Document.is_timbrado` es `False`).
|
|
50
|
+
"""
|
|
51
|
+
bag: dict[str, Any] = {'inputData': input_data}
|
|
52
|
+
if options is not None:
|
|
53
|
+
bag['options'] = options
|
|
54
|
+
|
|
55
|
+
data = self._client.call(self._BUILD_OPERATION, bag=bag)
|
|
56
|
+
return Document.from_api(data)
|
|
57
|
+
|
|
58
|
+
def build_signed(
|
|
59
|
+
self,
|
|
60
|
+
input_data: str | dict[str, Any],
|
|
61
|
+
*,
|
|
62
|
+
options: dict[str, Any] | None = None,
|
|
63
|
+
caf_xml: str,
|
|
64
|
+
certificate: Certificate,
|
|
65
|
+
) -> Document:
|
|
66
|
+
"""
|
|
67
|
+
Genera el DTE real, timbrado y firmado.
|
|
68
|
+
|
|
69
|
+
`input_data`/`options` funcionan igual que en `build_draft()`.
|
|
70
|
+
Requiere un CAF real (XML tal como lo entrega el SII, cubriendo
|
|
71
|
+
el folio indicado en `input_data`) y el certificado digital del
|
|
72
|
+
emisor. Para pruebas, ambos se pueden generar con
|
|
73
|
+
`IdentifierComponent.caf_faker` y
|
|
74
|
+
`TradingPartiesComponent.mandatario_manager`.
|
|
75
|
+
"""
|
|
76
|
+
bag: dict[str, Any] = {'inputData': input_data}
|
|
77
|
+
if options is not None:
|
|
78
|
+
bag['options'] = options
|
|
79
|
+
bag['caf'] = caf_xml
|
|
80
|
+
bag['certificate'] = certificate.to_payload()
|
|
81
|
+
|
|
82
|
+
data = self._client.call(self._BUILD_OPERATION, bag=bag)
|
|
83
|
+
return Document.from_api(data)
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.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.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/models.py
RENAMED
|
@@ -56,29 +56,29 @@ class DocumentBag:
|
|
|
56
56
|
Bolsa normalizada devuelta por `document.loader::loadXml`.
|
|
57
57
|
|
|
58
58
|
No trae el XML del documento ni `id` — solo la bolsa ya
|
|
59
|
-
normalizada: `
|
|
59
|
+
normalizada: `document` (`Encabezado`/`Detalle`), `document_type`
|
|
60
60
|
(metadatos del tipo de documento: `codigo`, `nombre`, `categoria`,
|
|
61
|
-
`es_boleta`, etc., sin tipar) y `
|
|
61
|
+
`es_boleta`, etc., sin tipar) y `document_stamp` (el TED, como XML
|
|
62
62
|
plano, no en base64 ni parseado). Sirve para reconstruir los datos
|
|
63
63
|
de un DTE ya emitido a partir de su XML.
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
document: dict[str, Any]
|
|
67
67
|
document_type: dict[str, Any]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
document_stamp: str | None
|
|
69
|
+
document_extra: dict[str, Any] | None
|
|
70
|
+
document_auth: dict[str, Any] | None
|
|
71
71
|
raw: dict[str, Any]
|
|
72
72
|
|
|
73
73
|
@classmethod
|
|
74
74
|
def from_api(cls, data: dict[str, Any]) -> DocumentBag:
|
|
75
75
|
"""Construye un `DocumentBag` desde el `data` de la API."""
|
|
76
76
|
return cls(
|
|
77
|
-
|
|
77
|
+
document=data['document'],
|
|
78
78
|
document_type=data['document_type'],
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
document_stamp=data.get('document_stamp'),
|
|
80
|
+
document_extra=data.get('document_extra'),
|
|
81
|
+
document_auth=data.get('document_auth'),
|
|
82
82
|
raw=data,
|
|
83
83
|
)
|
|
84
84
|
|
|
@@ -163,7 +163,7 @@ class Example:
|
|
|
163
163
|
"""
|
|
164
164
|
Un ejemplo de documento, tal como lo devuelve `examples::get()`.
|
|
165
165
|
|
|
166
|
-
`
|
|
166
|
+
`example` se pasa directamente al parámetro `input_data` de
|
|
167
167
|
`DocumentBuilderService.build_draft()`/`.build_signed()` — mismo
|
|
168
168
|
`Encabezado`/`Detalle` que usa el resto del SDK, sin transformación.
|
|
169
169
|
`expected` son los valores esperados del caso (totales, etc.) que usa
|
|
@@ -172,7 +172,7 @@ class Example:
|
|
|
172
172
|
"""
|
|
173
173
|
|
|
174
174
|
id: str
|
|
175
|
-
|
|
175
|
+
example: dict[str, Any]
|
|
176
176
|
expected: dict[str, Any]
|
|
177
177
|
|
|
178
178
|
@classmethod
|
|
@@ -180,7 +180,7 @@ class Example:
|
|
|
180
180
|
"""Construye un `Example` desde el `data` que devuelve la API."""
|
|
181
181
|
return cls(
|
|
182
182
|
id=data['id'],
|
|
183
|
-
|
|
183
|
+
example=data['example'],
|
|
184
184
|
expected=data.get('expected') or {},
|
|
185
185
|
)
|
|
186
186
|
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/renderer.py
RENAMED
|
@@ -32,6 +32,7 @@ class DocumentRendererService:
|
|
|
32
32
|
*,
|
|
33
33
|
format: str = 'pdf', # noqa: A002
|
|
34
34
|
renderings: dict[str, int] | None = None,
|
|
35
|
+
libredte_data: dict[str, Any] | None = None,
|
|
35
36
|
) -> RenderResult:
|
|
36
37
|
"""
|
|
37
38
|
Genera el PDF (u otro formato soportado por la API) de un documento.
|
|
@@ -48,16 +49,24 @@ class DocumentRendererService:
|
|
|
48
49
|
generarse (ej. pedir solo `'cedible'` para un tipo de documento
|
|
49
50
|
sin acuse de recibo). Ver `RenderResult` para cómo se identifica
|
|
50
51
|
cada copia en la respuesta.
|
|
52
|
+
|
|
53
|
+
`libredte_data` son datos adicionales que no están en el XML del
|
|
54
|
+
documento pero sí se usan al renderizar la plantilla — ej.
|
|
55
|
+
`{'extra': {'dte': {...}, 'historial': [...]}}`: lo que va en
|
|
56
|
+
`extra.dte` se combina con los datos del documento antes de
|
|
57
|
+
renderizar; el resto de `extra` (ej. `historial`) queda
|
|
58
|
+
disponible aparte para la plantilla.
|
|
51
59
|
"""
|
|
52
60
|
renderer_options: dict[str, Any] = {'format': format}
|
|
53
61
|
if renderings is not None:
|
|
54
62
|
renderer_options['renderings'] = renderings
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
bag: dict[str, Any] = {
|
|
65
|
+
'xmlDocument': document_xml_base64,
|
|
66
|
+
'options': {'renderer': renderer_options},
|
|
67
|
+
}
|
|
68
|
+
if libredte_data is not None:
|
|
69
|
+
bag['libredteData'] = libredte_data
|
|
70
|
+
|
|
71
|
+
data = self._client.call(self._RENDER_OPERATION, bag=bag)
|
|
63
72
|
return RenderResult.from_api(data)
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/models.py
RENAMED
|
@@ -193,11 +193,11 @@ class GetDocumentSiiReceptionDateResponse:
|
|
|
193
193
|
Fecha de recepción de un DTE en el SII (`getDocumentSiiReceptionDate`).
|
|
194
194
|
|
|
195
195
|
Si el documento consultado no tiene fecha de recepción registrada,
|
|
196
|
-
la API levanta `LibreDteApiError` — `
|
|
196
|
+
la API levanta `LibreDteApiError` — `fecha_recepcion_sii` nunca es
|
|
197
197
|
`None`.
|
|
198
198
|
"""
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
fecha_recepcion_sii: datetime
|
|
201
201
|
raw: dict[str, Any]
|
|
202
202
|
|
|
203
203
|
@classmethod
|
|
@@ -207,7 +207,7 @@ class GetDocumentSiiReceptionDateResponse:
|
|
|
207
207
|
) -> GetDocumentSiiReceptionDateResponse:
|
|
208
208
|
"""Construye desde el `data` que devuelve la API."""
|
|
209
209
|
return cls(
|
|
210
|
-
|
|
210
|
+
fecha_recepcion_sii=datetime.fromisoformat(
|
|
211
211
|
data['fecha_recepcion_sii'],
|
|
212
212
|
),
|
|
213
213
|
raw=data,
|
|
@@ -16,16 +16,16 @@ class Aec(XmlPayloadMixin):
|
|
|
16
16
|
"""
|
|
17
17
|
AEC (Archivo Electrónico de Cesión) construido (`aec::build`).
|
|
18
18
|
|
|
19
|
-
`
|
|
19
|
+
`aec` es la estructura ya parseada (bajo la clave `AEC` en la
|
|
20
20
|
respuesta cruda de la API), `xml_base64` el XML completo — permite
|
|
21
21
|
encadenar a `AecService.validate_schema`/`.validate_signature` o a
|
|
22
22
|
`SiiRtcService.send_aec`.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
aec: dict[str, Any]
|
|
26
26
|
xml_base64: str
|
|
27
27
|
|
|
28
28
|
@classmethod
|
|
29
29
|
def from_api(cls, data: dict[str, Any]) -> Aec:
|
|
30
30
|
"""Construye un `Aec` desde el `data` que devuelve la API."""
|
|
31
|
-
return cls(
|
|
31
|
+
return cls(aec=data.get('AEC') or {}, xml_base64=data['xml'])
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/trading_parties/models.py
RENAMED
|
@@ -30,8 +30,8 @@ class Certificate:
|
|
|
30
30
|
|
|
31
31
|
certificate: str
|
|
32
32
|
private_key: str
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
id: str | None = None
|
|
34
|
+
name: str | None = None
|
|
35
35
|
email: str | None = None
|
|
36
36
|
valid_from: datetime | None = None
|
|
37
37
|
valid_until: datetime | None = None
|
|
@@ -63,8 +63,8 @@ class Certificate:
|
|
|
63
63
|
return cls(
|
|
64
64
|
certificate=data['cert'],
|
|
65
65
|
private_key=data['pkey'],
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
id=data.get('id'),
|
|
67
|
+
name=data.get('name'),
|
|
68
68
|
email=data.get('email'),
|
|
69
69
|
valid_from=(
|
|
70
70
|
datetime.fromisoformat(data['from'])
|
|
@@ -89,13 +89,13 @@ class Certificate:
|
|
|
89
89
|
class Mandatario:
|
|
90
90
|
"""Mandatario (representante) dueño de un certificado digital."""
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
run: str
|
|
93
93
|
nombre: str
|
|
94
94
|
email: str | None = None
|
|
95
95
|
|
|
96
96
|
def to_payload(self) -> dict[str, Any]:
|
|
97
97
|
"""Payload esperado por la API para un mandatario."""
|
|
98
|
-
payload: dict[str, Any] = {'run': self.
|
|
98
|
+
payload: dict[str, Any] = {'run': self.run, 'nombre': self.nombre}
|
|
99
99
|
if self.email is not None:
|
|
100
100
|
payload['email'] = self.email
|
|
101
101
|
return payload
|
|
@@ -104,7 +104,7 @@ class Mandatario:
|
|
|
104
104
|
def from_api(cls, data: dict[str, Any]) -> Mandatario:
|
|
105
105
|
"""Construye un `Mandatario` desde el `data` que devuelve la API."""
|
|
106
106
|
return cls(
|
|
107
|
-
|
|
107
|
+
run=data['run'],
|
|
108
108
|
nombre=data['nombre'],
|
|
109
109
|
email=data.get('email'),
|
|
110
110
|
)
|
|
@@ -34,14 +34,14 @@ class LibreDTE:
|
|
|
34
34
|
sdk.billing.integration,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
borrador = doc.builder.build_draft(
|
|
37
|
+
borrador = doc.builder.build_draft(input_data)
|
|
38
38
|
|
|
39
39
|
caf = ident.caf_faker.create(emisor, codigo_documento=33)
|
|
40
40
|
certificate = tp.mandatario_manager.create_fake_certificate(
|
|
41
41
|
mandatario,
|
|
42
42
|
)
|
|
43
43
|
documento = doc.builder.build_signed(
|
|
44
|
-
|
|
44
|
+
input_data, caf_xml=caf.xml_base64, certificate=certificate,
|
|
45
45
|
)
|
|
46
46
|
sobre = doc.dispatcher.create(
|
|
47
47
|
documento.xml_base64, certificate=certificate, emisor=emisor_dto,
|
|
@@ -17,7 +17,7 @@ import pytest
|
|
|
17
17
|
from libredte_lib_sdk import LibreDTE
|
|
18
18
|
from libredte_lib_sdk.billing.trading_parties import Mandatario
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
_INPUT_DATA = {
|
|
21
21
|
'Encabezado': {
|
|
22
22
|
'IdDoc': {'TipoDTE': 33, 'Folio': 1},
|
|
23
23
|
'Emisor': {
|
|
@@ -38,7 +38,7 @@ _PARSED_DATA = {
|
|
|
38
38
|
'Detalle': [{'NmbItem': 'Producto A', 'QtyItem': 1, 'PrcItem': 1000}],
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
_INPUT_DATA_BOLETA = {
|
|
42
42
|
'Encabezado': {
|
|
43
43
|
'IdDoc': {'TipoDTE': 39, 'Folio': 1},
|
|
44
44
|
'Emisor': {
|
|
@@ -68,7 +68,7 @@ def real_sdk():
|
|
|
68
68
|
@pytest.fixture
|
|
69
69
|
def draft_document(real_sdk):
|
|
70
70
|
"""Un borrador real (factura afecta, tipo 33), construido en vivo."""
|
|
71
|
-
return real_sdk.billing.document.builder.build_draft(
|
|
71
|
+
return real_sdk.billing.document.builder.build_draft(_INPUT_DATA)
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
@pytest.fixture
|
|
@@ -80,7 +80,7 @@ def draft_boleta(real_sdk):
|
|
|
80
80
|
API omite en silencio una presentación `'cedible'` pedida sobre un
|
|
81
81
|
tipo de documento que no la soporta (ver `RenderResult`).
|
|
82
82
|
"""
|
|
83
|
-
return real_sdk.billing.document.builder.build_draft(
|
|
83
|
+
return real_sdk.billing.document.builder.build_draft(_INPUT_DATA_BOLETA)
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
@pytest.fixture
|
|
@@ -111,7 +111,7 @@ def signed_document(real_sdk, fake_certificate):
|
|
|
111
111
|
folio_hasta=100,
|
|
112
112
|
)
|
|
113
113
|
return real_sdk.billing.document.builder.build_signed(
|
|
114
|
-
|
|
114
|
+
_INPUT_DATA,
|
|
115
115
|
caf_xml=caf.xml_base64,
|
|
116
116
|
certificate=fake_certificate,
|
|
117
117
|
)
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_certificate_loader.py
RENAMED
|
@@ -24,7 +24,7 @@ from libredte_lib_sdk.billing.trading_parties import Mandatario
|
|
|
24
24
|
pytestmark = pytest.mark.live
|
|
25
25
|
|
|
26
26
|
_MANDATARIO = Mandatario(
|
|
27
|
-
|
|
27
|
+
run='76192083-9',
|
|
28
28
|
nombre='SASCO SpA',
|
|
29
29
|
email='demo@sasco.example',
|
|
30
30
|
)
|
|
@@ -56,8 +56,8 @@ def test_load_extracts_the_mandatario_and_a_usable_certificate(
|
|
|
56
56
|
):
|
|
57
57
|
loaded = real_sdk.system.certificate.loader.load(fake_pkcs12, _PASSWORD)
|
|
58
58
|
|
|
59
|
-
assert loaded.
|
|
60
|
-
assert loaded.
|
|
59
|
+
assert loaded.id == '76192083-9'
|
|
60
|
+
assert loaded.name == 'SASCO SpA'
|
|
61
61
|
assert loaded.email == 'demo@sasco.example'
|
|
62
62
|
assert loaded.is_active is True
|
|
63
63
|
assert loaded.certificate.startswith('-----BEGIN CERTIFICATE-----')
|
|
@@ -69,6 +69,6 @@ def test_create_from_certificate_matches_load(fake_pkcs12, real_sdk):
|
|
|
69
69
|
mandatario_manager = real_sdk.billing.trading_parties.mandatario_manager
|
|
70
70
|
mandatario = mandatario_manager.create_from_certificate(loaded)
|
|
71
71
|
|
|
72
|
-
assert mandatario.
|
|
73
|
-
assert mandatario.nombre == loaded.
|
|
72
|
+
assert mandatario.run == loaded.id
|
|
73
|
+
assert mandatario.nombre == loaded.name
|
|
74
74
|
assert mandatario.email == loaded.email
|
|
@@ -6,7 +6,7 @@ Tests en vivo para `DocumentExamplesService`.
|
|
|
6
6
|
|
|
7
7
|
Los ejemplos son los mismos casos de prueba, ya validados, de
|
|
8
8
|
`libredte-lib-core` (`tests/fixtures/yaml/documentos_ok/`) — se prueba
|
|
9
|
-
que `get()` entrega un `
|
|
9
|
+
que `get()` entrega un `example` realmente utilizable por
|
|
10
10
|
`DocumentBuilderService`, no solo que la llamada HTTP responde.
|
|
11
11
|
"""
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ pytestmark = pytest.mark.live
|
|
|
20
20
|
|
|
21
21
|
_EMISOR = {'rut': '76192083-9', 'razon_social': 'SASCO SpA'}
|
|
22
22
|
_MANDATARIO = Mandatario(
|
|
23
|
-
|
|
23
|
+
run='76192083-9',
|
|
24
24
|
nombre='SASCO SpA',
|
|
25
25
|
email='demo@sasco.example',
|
|
26
26
|
)
|
|
@@ -34,19 +34,19 @@ def test_list_returns_many_examples_across_document_types(real_sdk):
|
|
|
34
34
|
assert len(categories) > 1
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def
|
|
37
|
+
def test_get_returns_example_and_expected_values(real_sdk):
|
|
38
38
|
first = real_sdk.billing.document.examples.list()[0]
|
|
39
39
|
|
|
40
40
|
example = real_sdk.billing.document.examples.get(first.id)
|
|
41
41
|
|
|
42
42
|
assert example.id == first.id
|
|
43
|
-
assert 'Encabezado' in example.
|
|
44
|
-
assert 'Detalle' in example.
|
|
43
|
+
assert 'Encabezado' in example.example
|
|
44
|
+
assert 'Detalle' in example.example
|
|
45
45
|
assert example.expected
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def test_an_example_builds_into_a_real_signed_document(real_sdk):
|
|
49
|
-
"""El `
|
|
49
|
+
"""El `example` de un ejemplo debe servir tal cual a `build_signed`."""
|
|
50
50
|
b = real_sdk.billing
|
|
51
51
|
example = next(
|
|
52
52
|
e
|
|
@@ -57,14 +57,14 @@ def test_an_example_builds_into_a_real_signed_document(real_sdk):
|
|
|
57
57
|
|
|
58
58
|
caf = b.identifier.caf_faker.create(
|
|
59
59
|
_EMISOR,
|
|
60
|
-
codigo_documento=data.
|
|
60
|
+
codigo_documento=data.example['Encabezado']['IdDoc']['TipoDTE'],
|
|
61
61
|
)
|
|
62
62
|
certificate = b.trading_parties.mandatario_manager.create_fake_certificate(
|
|
63
63
|
_MANDATARIO,
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
documento = b.document.builder.build_signed(
|
|
67
|
-
data.
|
|
67
|
+
data.example,
|
|
68
68
|
caf_xml=caf.xml_base64,
|
|
69
69
|
certificate=certificate,
|
|
70
70
|
)
|
|
@@ -50,7 +50,7 @@ _RESULTADO_DTE = {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def
|
|
53
|
+
def test_build_envio_recibos_returns_the_datos_and_the_xml(
|
|
54
54
|
fake_certificate,
|
|
55
55
|
real_sdk,
|
|
56
56
|
):
|
|
@@ -82,7 +82,7 @@ def test_build_envio_recibos_result_chains_into_validator(
|
|
|
82
82
|
assert len(results) >= 1
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def
|
|
85
|
+
def test_build_respuesta_envio_returns_the_datos_and_the_xml(
|
|
86
86
|
fake_certificate,
|
|
87
87
|
real_sdk,
|
|
88
88
|
):
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_ownership_transfer.py
RENAMED
|
@@ -36,7 +36,7 @@ def built_aec(signed_document, fake_certificate, real_sdk):
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def test_build_returns_the_datos_and_the_xml(built_aec):
|
|
40
40
|
assert (
|
|
41
41
|
built_aec.datos['DocumentoAEC']['Caratula']['RutCedente']
|
|
42
42
|
== '76192083-9'
|
|
@@ -42,7 +42,7 @@ def test_certificate_from_api_maps_cert_and_pkey_fields():
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def test_mandatario_to_payload_omits_email_when_absent():
|
|
45
|
-
mandatario = Mandatario(
|
|
45
|
+
mandatario = Mandatario(run='76192083-9', nombre='SASCO SpA')
|
|
46
46
|
|
|
47
47
|
assert mandatario.to_payload() == {
|
|
48
48
|
'run': '76192083-9',
|
|
@@ -52,7 +52,7 @@ def test_mandatario_to_payload_omits_email_when_absent():
|
|
|
52
52
|
|
|
53
53
|
def test_mandatario_to_payload_includes_email_when_present():
|
|
54
54
|
mandatario = Mandatario(
|
|
55
|
-
|
|
55
|
+
run='76192083-9',
|
|
56
56
|
nombre='SASCO SpA',
|
|
57
57
|
email='sasco@example.com',
|
|
58
58
|
)
|
|
@@ -42,7 +42,7 @@ _REAL_SIGNED_DOCUMENT_RESPONSE = {
|
|
|
42
42
|
},
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
_INPUT_DATA = {
|
|
46
46
|
'Encabezado': {
|
|
47
47
|
'IdDoc': {'TipoDTE': 33, 'Folio': 1},
|
|
48
48
|
'Emisor': {'RUTEmisor': '76192083-9', 'RznSoc': 'SASCO SpA'},
|
|
@@ -64,14 +64,35 @@ def test_build_draft_omits_caf_and_certificate_from_the_payload(sdk):
|
|
|
64
64
|
f'{TEST_BASE_URL}/billing/document/builder/build',
|
|
65
65
|
).mock(return_value=httpx.Response(200, json=draft_response))
|
|
66
66
|
|
|
67
|
-
document = sdk.billing.document.builder.build_draft(
|
|
67
|
+
document = sdk.billing.document.builder.build_draft(_INPUT_DATA)
|
|
68
68
|
|
|
69
69
|
sent = json.loads(route.calls.last.request.content)
|
|
70
|
-
assert sent['parameters']['bag'].keys() == {'
|
|
70
|
+
assert sent['parameters']['bag'].keys() == {'inputData'}
|
|
71
71
|
assert document.id == '76192083-9_T033F000000001'
|
|
72
72
|
assert document.is_timbrado is False
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
@respx.mock
|
|
76
|
+
def test_build_draft_sends_options_when_given(sdk):
|
|
77
|
+
draft_response = {
|
|
78
|
+
'meta': _REAL_SIGNED_DOCUMENT_RESPONSE['meta'],
|
|
79
|
+
'data': {**_REAL_SIGNED_DOCUMENT_RESPONSE['data'], 'ted': None},
|
|
80
|
+
}
|
|
81
|
+
route = respx.post(
|
|
82
|
+
f'{TEST_BASE_URL}/billing/document/builder/build',
|
|
83
|
+
).mock(return_value=httpx.Response(200, json=draft_response))
|
|
84
|
+
|
|
85
|
+
sdk.billing.document.builder.build_draft(
|
|
86
|
+
'<DTE>...</DTE>',
|
|
87
|
+
options={'parser': {'strategy': 'default.xml'}},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
sent = json.loads(route.calls.last.request.content)
|
|
91
|
+
bag = sent['parameters']['bag']
|
|
92
|
+
assert bag['inputData'] == '<DTE>...</DTE>'
|
|
93
|
+
assert bag['options'] == {'parser': {'strategy': 'default.xml'}}
|
|
94
|
+
|
|
95
|
+
|
|
75
96
|
@respx.mock
|
|
76
97
|
def test_build_signed_includes_caf_and_certificate(sdk):
|
|
77
98
|
route = respx.post(
|
|
@@ -81,7 +102,7 @@ def test_build_signed_includes_caf_and_certificate(sdk):
|
|
|
81
102
|
)
|
|
82
103
|
|
|
83
104
|
document = sdk.billing.document.builder.build_signed(
|
|
84
|
-
|
|
105
|
+
_INPUT_DATA,
|
|
85
106
|
caf_xml='<AUTORIZACION/>',
|
|
86
107
|
certificate=_CERTIFICATE,
|
|
87
108
|
)
|
|
@@ -220,15 +241,15 @@ def test_mandatario_manager_create_fake_certificate(sdk):
|
|
|
220
241
|
|
|
221
242
|
service = sdk.billing.trading_parties.mandatario_manager
|
|
222
243
|
certificate = service.create_fake_certificate(
|
|
223
|
-
Mandatario(
|
|
244
|
+
Mandatario(run='76192083-9', nombre='SASCO SpA'),
|
|
224
245
|
)
|
|
225
246
|
|
|
226
247
|
sent = json.loads(route.calls.last.request.content)['parameters']
|
|
227
248
|
assert sent['mandatario']['run'] == '76192083-9'
|
|
228
249
|
assert certificate.certificate == 'CERT-PEM'
|
|
229
250
|
assert certificate.private_key == 'PKEY-PEM'
|
|
230
|
-
assert certificate.
|
|
231
|
-
assert certificate.
|
|
251
|
+
assert certificate.id == '76192083-9'
|
|
252
|
+
assert certificate.name == 'SASCO SpA'
|
|
232
253
|
assert certificate.email == 'sasco@example.com'
|
|
233
254
|
assert certificate.is_active is True
|
|
234
255
|
assert certificate.issuer == 'Derafu Test Certificate Authority'
|
|
@@ -451,6 +472,33 @@ def test_renderer_render_omits_renderings_key_by_default(sdk):
|
|
|
451
472
|
|
|
452
473
|
sent = json.loads(route.calls.last.request.content)['parameters']
|
|
453
474
|
assert 'renderings' not in sent['bag']['options']['renderer']
|
|
475
|
+
assert 'libredteData' not in sent['bag']
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@respx.mock
|
|
479
|
+
def test_renderer_render_sends_libredte_data_when_given(sdk):
|
|
480
|
+
"""`libredte_data` viaja como `bag.libredteData` cuando se indica."""
|
|
481
|
+
route = respx.post(
|
|
482
|
+
f'{TEST_BASE_URL}/billing/document/renderer/render',
|
|
483
|
+
).mock(
|
|
484
|
+
return_value=httpx.Response(
|
|
485
|
+
200,
|
|
486
|
+
json={
|
|
487
|
+
'meta': {},
|
|
488
|
+
'data': {'renderings': [_rendering_response(b'ok')]},
|
|
489
|
+
},
|
|
490
|
+
),
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
sdk.billing.document.renderer.render(
|
|
494
|
+
'ZG9jdW1lbnRv',
|
|
495
|
+
libredte_data={'extra': {'historial': ['evento 1']}},
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
sent = json.loads(route.calls.last.request.content)['parameters']
|
|
499
|
+
assert sent['bag']['libredteData'] == {
|
|
500
|
+
'extra': {'historial': ['evento 1']},
|
|
501
|
+
}
|
|
454
502
|
|
|
455
503
|
|
|
456
504
|
@respx.mock
|
|
@@ -492,7 +540,7 @@ def test_document_examples_get_sends_id_and_splits_expected(sdk):
|
|
|
492
540
|
'meta': {},
|
|
493
541
|
'data': {
|
|
494
542
|
'id': '033_factura_afecta/033_001_simple',
|
|
495
|
-
'example':
|
|
543
|
+
'example': _INPUT_DATA,
|
|
496
544
|
'expected': {
|
|
497
545
|
'Encabezado': {'Totales': {'MntTotal': 1190}},
|
|
498
546
|
},
|
|
@@ -507,7 +555,7 @@ def test_document_examples_get_sends_id_and_splits_expected(sdk):
|
|
|
507
555
|
|
|
508
556
|
sent = json.loads(route.calls.last.request.content)['parameters']
|
|
509
557
|
assert sent['id'] == '033_factura_afecta/033_001_simple'
|
|
510
|
-
assert example.
|
|
558
|
+
assert example.example == _INPUT_DATA
|
|
511
559
|
assert example.expected['Encabezado']['Totales']['MntTotal'] == 1190
|
|
512
560
|
|
|
513
561
|
|
|
@@ -538,7 +586,7 @@ def test_mandatario_manager_create_from_certificate(sdk):
|
|
|
538
586
|
'certificate': 'cert-pem',
|
|
539
587
|
'privateKey': 'key-pem',
|
|
540
588
|
}
|
|
541
|
-
assert mandatario.
|
|
589
|
+
assert mandatario.run == '76192083-9'
|
|
542
590
|
assert mandatario.nombre == 'SASCO SpA'
|
|
543
591
|
assert mandatario.email == 'demo@sasco.example'
|
|
544
592
|
|
|
@@ -694,8 +742,8 @@ def test_certificate_loader_load_sends_base64_and_decodes_metadata(sdk):
|
|
|
694
742
|
assert (
|
|
695
743
|
base64.b64decode(sent['certificate']['data']) == b'contenido-del-p12'
|
|
696
744
|
)
|
|
697
|
-
assert loaded.
|
|
698
|
-
assert loaded.
|
|
745
|
+
assert loaded.id == '76192083-9'
|
|
746
|
+
assert loaded.name == 'SASCO SpA'
|
|
699
747
|
assert loaded.email == 'demo@sasco.example'
|
|
700
748
|
assert loaded.is_active is True
|
|
701
749
|
assert loaded.certificate == 'CERT-PEM'
|
|
@@ -990,7 +1038,7 @@ def test_sii_rcv_get_document_sii_reception_date_parses_the_datetime(sdk):
|
|
|
990
1038
|
certificate=Certificate(certificate='cert-pem', private_key='key-pem'),
|
|
991
1039
|
)
|
|
992
1040
|
|
|
993
|
-
assert result.
|
|
1041
|
+
assert result.fecha_recepcion_sii.isoformat() == '2025-01-02T10:30:00'
|
|
994
1042
|
assert result.raw == {'fecha_recepcion_sii': '2025-01-02 10:30:00'}
|
|
995
1043
|
|
|
996
1044
|
|
|
@@ -1106,7 +1154,7 @@ def test_aec_build_sends_cedente_cesionario_cesion(sdk):
|
|
|
1106
1154
|
}
|
|
1107
1155
|
assert sent['cesionario']['RazonSocial'] == 'Factoring S.A.'
|
|
1108
1156
|
assert sent['cesion']['MontoCesion'] == 119000
|
|
1109
|
-
assert result.
|
|
1157
|
+
assert result.aec['DocumentoAEC']['Caratula']['RutCedente'] == (
|
|
1110
1158
|
'76192083-9'
|
|
1111
1159
|
)
|
|
1112
1160
|
assert result.xml_base64 == 'PEFFQz48L0FFQz4='
|
|
@@ -1170,11 +1218,11 @@ def test_document_loader_load_xml_decodes_the_full_bag(sdk):
|
|
|
1170
1218
|
|
|
1171
1219
|
sent = json.loads(route.calls.last.request.content)['parameters']
|
|
1172
1220
|
assert sent['xml'] == 'RFRFeG1s'
|
|
1173
|
-
assert result.
|
|
1221
|
+
assert result.document == {'Encabezado': {}}
|
|
1174
1222
|
assert result.document_type == {'codigo': 33, 'nombre': 'Factura'}
|
|
1175
|
-
assert result.
|
|
1176
|
-
assert result.
|
|
1177
|
-
assert result.
|
|
1223
|
+
assert result.document_stamp == '<TED/>'
|
|
1224
|
+
assert result.document_extra is None
|
|
1225
|
+
assert result.document_auth is None
|
|
1178
1226
|
|
|
1179
1227
|
|
|
1180
1228
|
_BOOK_BAG = {
|
|
@@ -1279,8 +1327,8 @@ def test_book_loader_load_decodes_book_type_caratula_and_detalle(sdk):
|
|
|
1279
1327
|
|
|
1280
1328
|
# `book`/`book_auth` siempre vienen `None` desde `load()` (no
|
|
1281
1329
|
# construye el libro) — ver docstring de `BookBag`.
|
|
1282
|
-
assert result.
|
|
1283
|
-
assert result.
|
|
1330
|
+
assert result.book is None
|
|
1331
|
+
assert result.book_auth is None
|
|
1284
1332
|
assert result.book_type == {
|
|
1285
1333
|
'codigo': 'libro_ventas',
|
|
1286
1334
|
'nombre': 'Libro de ventas',
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Copyright (C) 2026 LibreDTE <https://www.libredte.cl>
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
"""Servicio para `billing.document.builder`."""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from ...client import ApiClient
|
|
11
|
-
from ..trading_parties.models import Certificate
|
|
12
|
-
from .models import Document
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class DocumentBuilderService:
|
|
16
|
-
"""
|
|
17
|
-
Construye documentos tributarios (`billing.document.builder`).
|
|
18
|
-
|
|
19
|
-
El mismo worker de la API sirve tanto para un borrador como para el
|
|
20
|
-
documento timbrado y firmado, según qué datos se le pasen — acá se
|
|
21
|
-
separa en dos métodos explícitos para que la intención de cada
|
|
22
|
-
llamada quede clara en el código que la usa.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
_BUILD_OPERATION = 'billing.document.builder::build'
|
|
26
|
-
|
|
27
|
-
def __init__(self, client: ApiClient) -> None:
|
|
28
|
-
"""Guarda el `ApiClient` compartido usado para llamar a la API."""
|
|
29
|
-
self._client = client
|
|
30
|
-
|
|
31
|
-
def build_draft(self, parsed_data: dict[str, Any]) -> Document:
|
|
32
|
-
"""
|
|
33
|
-
Emite el borrador de un DTE a partir de datos ya normalizados.
|
|
34
|
-
|
|
35
|
-
`parsed_data` es el `Encabezado`/`Detalle` (y demás nodos) del
|
|
36
|
-
formato DTE del SII, incluyendo `Encabezado.IdDoc.Folio` (el SDK
|
|
37
|
-
no asigna folios: eso lo decide quien llama, típicamente porque
|
|
38
|
-
lleva el correlativo). Sin CAF ni certificado, el resultado no
|
|
39
|
-
queda timbrado (`Document.is_timbrado` es `False`).
|
|
40
|
-
"""
|
|
41
|
-
data = self._client.call(
|
|
42
|
-
self._BUILD_OPERATION,
|
|
43
|
-
bag={'parsedData': parsed_data},
|
|
44
|
-
)
|
|
45
|
-
return Document.from_api(data)
|
|
46
|
-
|
|
47
|
-
def build_signed(
|
|
48
|
-
self,
|
|
49
|
-
parsed_data: dict[str, Any],
|
|
50
|
-
*,
|
|
51
|
-
caf_xml: str,
|
|
52
|
-
certificate: Certificate,
|
|
53
|
-
) -> Document:
|
|
54
|
-
"""
|
|
55
|
-
Genera el DTE real, timbrado y firmado.
|
|
56
|
-
|
|
57
|
-
Requiere un CAF real (XML tal como lo entrega el SII, cubriendo
|
|
58
|
-
el folio indicado en `parsed_data`) y el certificado digital del
|
|
59
|
-
emisor. Para pruebas, ambos se pueden generar con
|
|
60
|
-
`IdentifierComponent.caf_faker` y
|
|
61
|
-
`TradingPartiesComponent.mandatario_manager`.
|
|
62
|
-
"""
|
|
63
|
-
data = self._client.call(
|
|
64
|
-
self._BUILD_OPERATION,
|
|
65
|
-
bag={
|
|
66
|
-
'parsedData': parsed_data,
|
|
67
|
-
'caf': caf_xml,
|
|
68
|
-
'certificate': certificate.to_payload(),
|
|
69
|
-
},
|
|
70
|
-
)
|
|
71
|
-
return Document.from_api(data)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/__init__.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/dispatcher.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/loader.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/models.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/__init__.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_faker.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/models.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/__init__.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_dte.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_rcv.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_rtc.py
RENAMED
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/ownership_transfer/aec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/certificate/__init__.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/certificate/loader.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/repository/__init__.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/repository/catalog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_rendering.py
RENAMED
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
{libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_operation_not_found.py
RENAMED
|
File without changes
|
|
File without changes
|