diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 3cc5aae5..b940d94e 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 0.3.0
+current_version = 0.4.0
commit = True
tag = True
diff --git a/.gitignore b/.gitignore
index 6d44c92d..36647d29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -271,3 +271,10 @@ Session.vim
# Auto-generated tag files
tags
+
+
+### Customizations for this project
+# warning: rules order matter. Do not move the ones in this section upwards!
+
+# note: prevent this dir from being ignored (because of rule `[Ss]cripts`)
+!/scripts/
diff --git a/HISTORY.rst b/HISTORY.rst
index 156f2111..dac52651 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,13 @@
History
-------
+0.4.0 (2019-04-16)
++++++++++++++++++++++++
+
+* (PR #16, 2019-04-16) dte.parse: change and improve ``clean_dte_xml``
+* (PR #14, 2019-04-09) data.ref: merge XML schemas dirs
+* (PR #13, 2019-04-09) extras: add Marshmallow field for a DTE's "tipo DTE"
+
0.3.0 (2019-04-05)
+++++++++++++++++++++++
diff --git a/MANIFEST.in b/MANIFEST.in
index 2d57a301..bedd0fc8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,6 +2,5 @@ include HISTORY.rst
include LICENSE
include README.rst
recursive-include cl_sii *py
-recursive-include cl_sii/data/ref/factura_electronica/schema_dte *.xsd
-recursive-include cl_sii/data/ref/factura_electronica/schema_iecv *.xsd
+recursive-include cl_sii/data/ref/factura_electronica/schemas-xml *.xsd
include cl_sii/py.typed
diff --git a/Makefile b/Makefile
index 0bfa3b51..429a1389 100644
--- a/Makefile
+++ b/Makefile
@@ -33,8 +33,8 @@ clean-test: ## remove test, lint and coverage artifacts
rm -rf .mypy_cache/
lint: ## run tools for code style analysis, static type check, etc
- flake8 --config=setup.cfg cl_sii tests
- mypy --config-file setup.cfg cl_sii
+ flake8 --config=setup.cfg cl_sii scripts tests
+ mypy --config-file setup.cfg cl_sii scripts
test: ## run tests quickly with the default Python
python setup.py test
diff --git a/cl_sii/__init__.py b/cl_sii/__init__.py
index dd17c5a4..795a5582 100644
--- a/cl_sii/__init__.py
+++ b/cl_sii/__init__.py
@@ -5,4 +5,4 @@
"""
-__version__ = '0.3.0'
+__version__ = '0.4.0'
diff --git a/cl_sii/contribuyente/constants.py b/cl_sii/contribuyente/constants.py
index 1062a936..eb049bd6 100644
--- a/cl_sii/contribuyente/constants.py
+++ b/cl_sii/contribuyente/constants.py
@@ -3,7 +3,7 @@
Source: XML types 'RznSocLargaType' and 'RznSocCortaType' in official schema
'SiiTypes_v10.xsd'.
-https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/SiiTypes_v10.xsd#L635-L651
+https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd#L635-L651
"""
diff --git a/cl_sii/data/ref/factura_electronica/schema_iecv/README.md b/cl_sii/data/ref/factura_electronica/schema_iecv/README.md
deleted file mode 100644
index 69717f73..00000000
--- a/cl_sii/data/ref/factura_electronica/schema_iecv/README.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# schema_iecv
-
-This directory contains all the files of `schema_iecv.zip`, plus this text file.
-All the files have been preserved as they were; schemas are in their original text encoding
-(ISO-8859-1) and have not been modified in the slightest way.
-
-The most significant structures are:
-- XML element `LceCal`: "Certificado Autorizacion de Libros, generado por el SII".
-- XML element `LceCoCertif`: "Comprobante de Certificacion".
-- XML element `LibroCompraVenta`: "Informacion Electronica de Libros de Compra y Venta".
-
-Notes:
-- IECV means "Información Electrónica de Libros de Compra y Venta".
-- LCE means "Libros Contables Electrónicos".
-
-
-## Source
-
-
-### Original & Official
-
-[schema_iecv.zip](http://www.sii.cl/factura_electronica/schema_iecv.zip) (2018-11-28),
-referenced from official webpage
-[SII](http://www.sii.cl)
-/ [Factura electrónica](http://www.sii.cl/servicios_online/1039-.html)
-/ [FORMATO XML DE DOCUMENTOS ELECTRÓNICOS](http://www.sii.cl/factura_electronica/formato_xml.htm)
-as
-"[Bajar schema XML de Información Electrónica de Compras y Ventas](http://www.sii.cl/factura_electronica/schema_iecv.zip)".
-
-
-### Updates
-
-Unfortunately the files available on SII's website are outdated with respect to the regulations
-(and even with respect to the documentation PDFs published alongside).
-
-Schema files will be updated as necessary, indicating the source in the corresponding commit.
-
-
-## Contents
-
-
-### Detail
-
-- `LceCal_v10.xsd`
- - XML target namespace: `http://www.sii.cl/SiiLce`.
- - XML included/imported schemas: `LceSiiTypes_v10.xsd`, `xmldsignature_v10.xsd`.
- - XML elements:
- - `LceCal`: "Certificado Autorizacion de Libros, generado por el SII".
-
-- `LceCoCertif_v10.xsd`:
- - XML target namespace: `http://www.sii.cl/SiiLce`.
- - XML included/imported schemas: `LceSiiTypes_v10.xsd`, `LceCal_v10.xsd`, `xmldsignature_v10.xsd`.
- - XML elements:
- - `LceCoCertif`: "Comprobante de Certificacion".
-
-- `LceSiiTypes_v10.xsd`:
- - XML target namespace: `http://www.sii.cl/SiiLce`.
- - XML included/imported schemas: none.
- - XML elements: none.
- - XML data types:
- - `RUTType`: "Rol Unico Tributario (99..99-X)".
- - `FolioType`: "Folio de DTE - 10 digitos".
- - `MontoType`: "Monto de 18 digitos y 4 decimales".
- - `ImptoType`: "Impuestos Adicionales".
- - `MntImpType`: "Monto 18 digitos (> cero)".
- - `PctType`: "Porcentaje (3 enteros y 2 decimales)".
- - `DoctoType`: "Tipos de Documentos".
- - `ValorType`: "Monto 18 digitos (positivo o negativo)".
- - `Periodo`: "lapso de tiempo. En forma AAAA-MM hasta AAAA-MM".
- - `MontoSinDecType`: "Monto 18 digitos (mayor o igual a cero)".
-
-- `LibroCV_v10.xsd`:
- - XML target namespace: `http://www.sii.cl/SiiDte` (**not** `http://www.sii.cl/SiiLce`).
- - XML included/imported schemas: `LceCoCertif_v10.xsd`, `xmldsignature_v10.xsd`.
- - XML elements:
- - `LibroCompraVenta`: "Informacion Electronica de Libros de Compra y Venta".
- - XML data types:
- - `RUTType`: "RUT 99999999-X".
- - `MontoType`: "Monto 18 digitos (mayor o igual a cero)".
- - `ValorType`: "Monto 18 digitos (positivo o negativo)".
- - `MntImpType`: "Monto 18 digitos (> cero)".
- - `ImptoType`: "Impuestos Adicionales".
- - `DoctoType`: "Tipos de Documentos".
- - `PctType`: "Porcentaje (3 enteros y 2 decimales)".
-
-- `xmldsignature_v10.xsd`:
- - XML target namespace: `http://www.w3.org/2000/09/xmldsig#`.
- - XML included/imported schemas: none.
- - XML elements:
- - `Signature`: "Firma Digital sobre Documento".
- - XML data types:
- - `SignatureType`: "Firma Digital con Restricciones".
-
-
-### Notes
-
-- File `LibroCV_v10.xsd` defines many data types that are already defined in `LceSiiTypes_v10.xsd`.
-- The two enums named `DoctoType` (one in `LibroCV_v10.xsd` and the other in `LceSiiTypes_v10.xsd`)
- **have different elements**.
-- File `xmldsignature_v10.xsd` is identical to `../schema_dte/xmldsignature_v10.xsd`.
diff --git a/cl_sii/data/ref/factura_electronica/schema_iecv/xmldsignature_v10.xsd b/cl_sii/data/ref/factura_electronica/schema_iecv/xmldsignature_v10.xsd
deleted file mode 100644
index 1137a846..00000000
--- a/cl_sii/data/ref/factura_electronica/schema_iecv/xmldsignature_v10.xsd
+++ /dev/null
@@ -1,184 +0,0 @@
-
-
-
-
-
- Firma Digital sobre Documento
-
-
-
-
- Firma Digital con Restricciones
-
-
-
-
- Descripcion de la Informacion Firmada y del Metodo de Firma
-
-
-
-
-
- Algoritmo de Canonicalizacion
-
-
-
-
-
-
-
- Algoritmo de Firma
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Referencia a Elemento Firmado
-
-
-
-
-
- Algoritmo de Transformacion
-
-
-
-
-
-
-
-
-
-
-
-
-
- Algoritmo de Digest
-
-
-
-
-
-
-
- Valor de Digest
-
-
-
-
-
-
-
-
-
-
-
- Valor de la Firma Digital
-
-
-
-
- Informacion de Claves Publicas y Certificado
-
-
-
-
-
-
-
-
- Informacion de Claves Publicas RSA
-
-
-
-
-
- Modulo Clave RSA
-
-
-
-
- Exponente Clave RSA
-
-
-
-
-
-
-
- Informacion de Claves Publicas DSA
-
-
-
-
-
- Modulo Primo
-
-
-
-
- Entero Divisor de P - 1
-
-
-
-
- Entero f(P, Q)
-
-
-
-
- G**X mod P
-
-
-
-
-
-
-
-
-
-
- Informacion del Certificado Publico
-
-
-
-
-
- Certificado Publico
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cl_sii/data/ref/factura_electronica/schema_dte/DTE_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/DTE_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_dte/DTE_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/DTE_v10.xsd
diff --git a/cl_sii/data/ref/factura_electronica/schema_dte/EnvioDTE_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/EnvioDTE_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_dte/EnvioDTE_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/EnvioDTE_v10.xsd
diff --git a/cl_sii/data/ref/factura_electronica/schema_iecv/LceCal_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/LceCal_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_iecv/LceCal_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/LceCal_v10.xsd
diff --git a/cl_sii/data/ref/factura_electronica/schema_iecv/LceCoCertif_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/LceCoCertif_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_iecv/LceCoCertif_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/LceCoCertif_v10.xsd
diff --git a/cl_sii/data/ref/factura_electronica/schema_iecv/LceSiiTypes_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/LceSiiTypes_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_iecv/LceSiiTypes_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/LceSiiTypes_v10.xsd
diff --git a/cl_sii/data/ref/factura_electronica/schema_iecv/LibroCV_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/LibroCV_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_iecv/LibroCV_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/LibroCV_v10.xsd
diff --git a/cl_sii/data/ref/factura_electronica/schema_dte/README.md b/cl_sii/data/ref/factura_electronica/schemas-xml/README.md
similarity index 51%
rename from cl_sii/data/ref/factura_electronica/schema_dte/README.md
rename to cl_sii/data/ref/factura_electronica/schemas-xml/README.md
index c48d4483..04c5d36e 100644
--- a/cl_sii/data/ref/factura_electronica/schema_dte/README.md
+++ b/cl_sii/data/ref/factura_electronica/schemas-xml/README.md
@@ -1,21 +1,37 @@
-# schema_dte
+# SII "factura_electronica" / XML schemas
-This directory contains all the files of `schema_dte.zip`, plus this text file.
-All the files have been preserved as they were; schemas are in their original text encoding
-(ISO-8859-1) and have not been modified in the slightest way.
+This directory contains all the files of `schema_dte.zip` and `schema_iecv.zip`,
+plus this text file.
The most significant structures are:
-- XML element `EnvioDTE`: "Envio de Documentos Tributarios Electronicos".
-- XML data type `DTEDefType`: "Documento Tributario Electronico".
-
-Note: DTE means "Documento Tributario Electrónico".
+- common:
+ - XML element `Signature`: "Firma Digital sobre Documento".
+ - XML data type `SignatureType`: "Firma Digital con Restricciones".
+- DTE:
+ - XML element `EnvioDTE`: "Envio de Documentos Tributarios Electronicos".
+ - XML data type `DTEDefType`: "Documento Tributario Electronico".
+- IECV:
+ - XML element `LceCal`: "Certificado Autorizacion de Libros, generado por el SII".
+ - XML element `LceCoCertif`: "Comprobante de Certificacion".
+ - XML element `LibroCompraVenta`: "Informacion Electronica de Libros de Compra y Venta".
+
+Note:
+- DTE means "Documento Tributario Electrónico".
+- IECV means "Información Electrónica de Libros de Compra y Venta".
+- LCE means "Libros Contables Electrónicos".
## Source
+All the files were preserved as they were but later on updates were applied (even unofficial ones).
+Files are kept in their original text encoding (ISO-8859-1).
+
### Original & Official
+
+#### DTE
+
[schema_dte.zip](http://www.sii.cl/factura_electronica/schema_dte.zip) (2018-11-28),
referenced from official webpage
[SII](http://www.sii.cl)
@@ -25,6 +41,17 @@ as
"[Bajar schema XML de Documentos Tributarios Electrónicos](http://www.sii.cl/factura_electronica/schema_dte.zip) (Incluye Documentos de exportación)"
+#### IECV
+
+[schema_iecv.zip](http://www.sii.cl/factura_electronica/schema_iecv.zip) (2018-11-28),
+referenced from official webpage
+[SII](http://www.sii.cl)
+/ [Factura electrónica](http://www.sii.cl/servicios_online/1039-.html)
+/ [FORMATO XML DE DOCUMENTOS ELECTRÓNICOS](http://www.sii.cl/factura_electronica/formato_xml.htm)
+as
+"[Bajar schema XML de Información Electrónica de Compras y Ventas](http://www.sii.cl/factura_electronica/schema_iecv.zip)".
+
+
### Updates
Unfortunately the files available on SII's website are outdated with respect to the regulations
@@ -38,6 +65,20 @@ Schema files will be updated as necessary, indicating the source in the correspo
### Detail
+
+#### Common
+
+- `xmldsignature_v10.xsd`:
+ - XML target namespace: `http://www.w3.org/2000/09/xmldsig#`.
+ - XML included/imported schemas: none.
+ - XML elements:
+ - `Signature`: "Firma Digital sobre Documento".
+ - XML data types:
+ - `SignatureType`: "Firma Digital con Restricciones".
+
+
+#### DTE
+
- `DTE_v10.xsd`: "XSD principal y que incluye a los 3" otros XSD.
- XML target namespace: `http://www.sii.cl/SiiDte`.
- XML included/imported schemas: `SiiTypes_v10.xsd`, `xmldsignature_v10.xsd`.
@@ -96,6 +137,51 @@ Schema files will be updated as necessary, indicating the source in the correspo
- `PctType`: "Monto de Porcentaje ( 3 y 2)".
+#### IECV
+
+- `LceCal_v10.xsd`
+ - XML target namespace: `http://www.sii.cl/SiiLce`.
+ - XML included/imported schemas: `LceSiiTypes_v10.xsd`, `xmldsignature_v10.xsd`.
+ - XML elements:
+ - `LceCal`: "Certificado Autorizacion de Libros, generado por el SII".
+
+- `LceCoCertif_v10.xsd`:
+ - XML target namespace: `http://www.sii.cl/SiiLce`.
+ - XML included/imported schemas: `LceSiiTypes_v10.xsd`, `LceCal_v10.xsd`, `xmldsignature_v10.xsd`.
+ - XML elements:
+ - `LceCoCertif`: "Comprobante de Certificacion".
+
+- `LceSiiTypes_v10.xsd`:
+ - XML target namespace: `http://www.sii.cl/SiiLce`.
+ - XML included/imported schemas: none.
+ - XML elements: none.
+ - XML data types:
+ - `RUTType`: "Rol Unico Tributario (99..99-X)".
+ - `FolioType`: "Folio de DTE - 10 digitos".
+ - `MontoType`: "Monto de 18 digitos y 4 decimales".
+ - `ImptoType`: "Impuestos Adicionales".
+ - `MntImpType`: "Monto 18 digitos (> cero)".
+ - `PctType`: "Porcentaje (3 enteros y 2 decimales)".
+ - `DoctoType`: "Tipos de Documentos".
+ - `ValorType`: "Monto 18 digitos (positivo o negativo)".
+ - `Periodo`: "lapso de tiempo. En forma AAAA-MM hasta AAAA-MM".
+ - `MontoSinDecType`: "Monto 18 digitos (mayor o igual a cero)".
+
+- `LibroCV_v10.xsd`:
+ - XML target namespace: `http://www.sii.cl/SiiDte` (**not** `http://www.sii.cl/SiiLce`).
+ - XML included/imported schemas: `LceCoCertif_v10.xsd`, `xmldsignature_v10.xsd`.
+ - XML elements:
+ - `LibroCompraVenta`: "Informacion Electronica de Libros de Compra y Venta".
+ - XML data types:
+ - `RUTType`: "RUT 99999999-X".
+ - `MontoType`: "Monto 18 digitos (mayor o igual a cero)".
+ - `ValorType`: "Monto 18 digitos (positivo o negativo)".
+ - `MntImpType`: "Monto 18 digitos (> cero)".
+ - `ImptoType`: "Impuestos Adicionales".
+ - `DoctoType`: "Tipos de Documentos".
+ - `PctType`: "Porcentaje (3 enteros y 2 decimales)".
+
+
### Notes
- Enums `DOCType`, `DocType`, `DTEType` and `DTEFacturasType` (all of them in `SiiTypes_v10.xsd`)
@@ -106,4 +192,6 @@ Schema files will be updated as necessary, indicating the source in the correspo
- `DTEFacturasType`
- `LIQType`: "Tipos de Liquidaciones".
- `EXPType`: "Tipos de Facturas de Exportacion".
-- File `xmldsignature_v10.xsd` is identical to `../schema_iecv/xmldsignature_v10.xsd`.
+- File `LibroCV_v10.xsd` defines many data types that are already defined in `LceSiiTypes_v10.xsd`.
+- The two enums named `DoctoType` (one in `LibroCV_v10.xsd` and the other in `LceSiiTypes_v10.xsd`)
+ **have different elements**.
diff --git a/cl_sii/data/ref/factura_electronica/schema_dte/SiiTypes_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_dte/SiiTypes_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd
diff --git a/cl_sii/data/ref/factura_electronica/schema_dte/xmldsignature_v10.xsd b/cl_sii/data/ref/factura_electronica/schemas-xml/xmldsignature_v10.xsd
similarity index 100%
rename from cl_sii/data/ref/factura_electronica/schema_dte/xmldsignature_v10.xsd
rename to cl_sii/data/ref/factura_electronica/schemas-xml/xmldsignature_v10.xsd
diff --git a/cl_sii/dte/constants.py b/cl_sii/dte/constants.py
index f52ec186..4b489c1e 100644
--- a/cl_sii/dte/constants.py
+++ b/cl_sii/dte/constants.py
@@ -2,7 +2,7 @@
DTE-related constants.
Sources: official XML schemas 'SiiTypes_v10.xsd' and 'DTE_v10.xsd'.
-https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/
+https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/
"""
import enum
@@ -16,10 +16,10 @@
# - description: "Folio del Documento Electronico"
# - XML type: 'FolioType'
# - source:
-# https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/DTE_v10.xsd#L52-L56
+# https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/DTE_v10.xsd#L52-L56
# XML type 'FolioType' in official schema 'SiiTypes_v10.xsd'.
# - source:
-# https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/SiiTypes_v10.xsd#L153-L160
+# https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd#L153-L160
DTE_FOLIO_FIELD_TYPE = int
"""DTE field 'Folio' type."""
@@ -37,10 +37,10 @@
# - description: "Monto Total del DTE"
# - XML type: 'MontoType'
# - source:
-# https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/DTE_v10.xsd#L1160-L1164
+# https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/DTE_v10.xsd#L1160-L1164
# XML type 'MontoType' in official schema 'SiiTypes_v10.xsd'
# - source:
-# https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/SiiTypes_v10.xsd#L563-L570
+# https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd#L563-L570
DTE_MONTO_TOTAL_FIELD_TYPE = int
"""DTE field 'Monto Total' type."""
@@ -58,7 +58,7 @@
# - description: "Tipo de DTE"
# - XML type: 'DTEType'
# - source:
-# https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/DTE_v10.xsd#L47-L51
+# https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/DTE_v10.xsd#L47-L51
DTE_TIPO_DTE_FIELD_TYPE = int
"""DTE field 'Tipo de DTE' type."""
@@ -75,7 +75,7 @@ class TipoDteEnum(enum.IntEnum):
Enum of Tipo de DTE.
Source: XML type ``DTEType`` (enum) in official schema ``SiiTypes_v10.xsd``.
- https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/SiiTypes_v10.xsd#L63-L99
+ https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd#L63-L99
"""
diff --git a/cl_sii/dte/data_models.py b/cl_sii/dte/data_models.py
index 14a32553..fa7ac32d 100644
--- a/cl_sii/dte/data_models.py
+++ b/cl_sii/dte/data_models.py
@@ -1,7 +1,7 @@
import dataclasses
from dataclasses import field as dc_field
from datetime import date
-from typing import Dict, Optional
+from typing import Mapping, Optional
import cl_sii.contribuyente.constants
import cl_sii.rut.constants
@@ -113,7 +113,7 @@ def __post_init__(self) -> None:
validate_dte_folio(self.folio)
- def as_dict(self) -> Dict[str, object]:
+ def as_dict(self) -> Mapping[str, object]:
return dataclasses.asdict(self)
@property
diff --git a/cl_sii/dte/parse.py b/cl_sii/dte/parse.py
index 8da7e5ff..aac22134 100644
--- a/cl_sii/dte/parse.py
+++ b/cl_sii/dte/parse.py
@@ -7,7 +7,8 @@
>>> from cl_sii.dte import parse
>>> from cl_sii.libs import xml_utils
->>> with open('/dir/my_file.xml', mode='rb') as f:
+>>> xml_file_path = '/dir/my_file.xml'
+>>> with open(xml_file_path, mode='rb') as f:
... xml_doc = xml_utils.parse_untrusted_xml(f.read())
>>> parse.clean_dte_xml(xml_doc)
@@ -16,11 +17,12 @@
>>> dte_struct = parse.parse_dte_xml(xml_doc)
"""
+import io
import logging
import os
from dataclasses import MISSING, _MISSING_TYPE
from datetime import date
-from typing import Optional, Union
+from typing import Optional, Tuple, Union
import lxml.etree
@@ -33,8 +35,18 @@
logger = logging.getLogger(__name__)
+DTE_XMLNS = 'http://www.sii.cl/SiiDte'
+"""
+XML namespace for DTE element in DTE XML schema.
+
+Ref: target namespace in 'DTE_v10.xsd' and 'EnvioDTE_v10.xsd'.
+
+* cl_sii/data/ref/factura_electronica/schemas-xml/DTE_v10.xsd#L19 (f57a326)
+* cl_sii/data/ref/factura_electronica/schemas-xml/EnvioDTE_v10.xsd#L14 (f57a326)
+"""
+
DTE_XMLNS_MAP = {
- 'sii-dte': 'http://www.sii.cl/SiiDte',
+ 'sii-dte': DTE_XMLNS,
}
"""
Mapping from XML namespace prefix to full name, for DTE processing.
@@ -44,7 +56,7 @@
_DTE_XML_SCHEMA_PATH = os.path.abspath(
os.path.join(
os.path.dirname(os.path.dirname(__file__)),
- 'data/ref/factura_electronica/schema_dte/EnvioDTE_v10.xsd',
+ 'data/ref/factura_electronica/schemas-xml/EnvioDTE_v10.xsd',
)
)
DTE_XML_SCHEMA_OBJ = xml_utils.read_xml_schema(_DTE_XML_SCHEMA_PATH)
@@ -59,29 +71,36 @@
# main functions
###############################################################################
-def clean_dte_xml(xml_doc: lxml.etree.ElementBase) -> bool:
+def clean_dte_xml(
+ xml_doc: lxml.etree.ElementBase,
+ set_missing_xmlns: bool = False,
+ remove_doc_personalizado: bool = True,
+) -> Tuple[lxml.etree.ElementBase, bool]:
"""
- Remove some non-compliant (DTE XML schema) data from ``xml_doc``.
+ Apply changes to ``xml_doc`` towards compliance to DTE XML schema.
+
+ .. seealso:: :data:`DTE_XML_SCHEMA_OBJ`
- Not all non-compliant data is removed; only some corresponding to popular
- modifications but non-compliant nonetheless.
+ There is a kwarg to enable/disable each kind of change.
- The object is modified in-place.
+ .. warning::
+ Do not assume the ``xml_doc``object is modified in-place because in
+ some cases it will be replaced (i.e. a entirely different object).
- :returns: whether ``xml_doc`` was modified or not
+ :returns: new ``xml_doc`` and whether it was modified or not
"""
modified = False
- xml_etree = xml_doc.getroottree()
+ if set_missing_xmlns:
+ xml_doc, _modified = _set_dte_xml_missing_xmlns(xml_doc)
+ modified = modified or _modified
- # Remove non-standard but popular element 'DocPersonalizado'.
- xml_em = xml_etree.find('sii-dte:DocPersonalizado', namespaces=DTE_XMLNS_MAP)
- if xml_em is not None:
- modified = True
- xml_doc.remove(xml_em)
+ if remove_doc_personalizado:
+ xml_doc, _modified = _remove_dte_xml_doc_personalizado(xml_doc)
+ modified = modified or _modified
- return modified
+ return xml_doc, modified
def validate_dte_xml(xml_doc: lxml.etree.ElementBase) -> None:
@@ -125,6 +144,58 @@ def parse_dte_xml(xml_doc: lxml.etree.ElementBase) -> data_models.DteDataL2:
# helpers
###############################################################################
+def _set_dte_xml_missing_xmlns(
+ xml_doc: lxml.etree.ElementBase,
+) -> Tuple[lxml.etree.ElementBase, bool]:
+
+ # source: name of the XML element without namespace.
+ # cl_sii/data/ref/factura_electronica/schemas-xml/DTE_v10.xsd#L22 (f57a326)
+ # cl_sii/data/ref/factura_electronica/schemas-xml/EnvioDTE_v10.xsd#L92 (f57a326)
+ em_tag_simple = 'DTE'
+
+ em_namespace = DTE_XMLNS
+ em_tag_namespaced = '{%s}%s' % (em_namespace, em_tag_simple)
+
+ # Tag of 'DTE' should be ...
+ assert em_tag_namespaced == '{http://www.sii.cl/SiiDte}DTE'
+
+ modified = False
+
+ root_em = xml_doc.getroottree().getroot()
+ root_em_tag = root_em.tag
+
+ if root_em_tag == em_tag_namespaced:
+ pass
+ elif root_em_tag == em_tag_simple:
+ modified = True
+ root_em.set('xmlns', em_namespace)
+ f = io.BytesIO()
+ xml_utils.write_xml_doc(xml_doc, f)
+ new_xml_doc_bytes = f.getvalue()
+ xml_doc = xml_utils.parse_untrusted_xml(new_xml_doc_bytes)
+ else:
+ exc_msg = "XML root element tag does not match the expected simple or namespaced name."
+ raise Exception(exc_msg, em_tag_simple, em_tag_namespaced, root_em_tag)
+
+ return xml_doc, modified
+
+
+def _remove_dte_xml_doc_personalizado(
+ xml_doc: lxml.etree.ElementBase,
+) -> Tuple[lxml.etree.ElementBase, bool]:
+ # Remove non-standard but popular element 'DocPersonalizado', it if exists.
+
+ modified = False
+ em_path = 'sii-dte:DocPersonalizado'
+
+ xml_em = xml_doc.getroottree().find(em_path, namespaces=DTE_XMLNS_MAP)
+ if xml_em is not None:
+ modified = True
+ xml_doc.remove(xml_em)
+
+ return xml_doc, modified
+
+
def _get_tipo_dte(xml_etree: lxml.etree.ElementTree) -> constants.TipoDteEnum:
em_path = 'sii-dte:Documento/sii-dte:Encabezado/sii-dte:IdDoc/sii-dte:TipoDTE'
diff --git a/cl_sii/extras/mm_fields.py b/cl_sii/extras/mm_fields.py
index d5eefc75..59b17535 100644
--- a/cl_sii/extras/mm_fields.py
+++ b/cl_sii/extras/mm_fields.py
@@ -13,6 +13,7 @@
import marshmallow.fields
+from cl_sii.dte.constants import TipoDteEnum
from cl_sii.rut import Rut
@@ -62,3 +63,57 @@ def _validated(self, value: Optional[object]) -> Optional[Rut]:
except ValueError:
self.fail('invalid')
return validated
+
+
+class TipoDteField(marshmallow.fields.Field):
+
+ """
+ Marshmallow field for a DTE's "tipo DTE".
+
+ Data types:
+ * native/primitive/internal/deserialized: :class:`TipoDteEnum`
+ * representation/serialized: int, same as for Marshmallow field
+ :class:`marshmallow.fields.Integer`
+
+ The field performs some input value cleaning when it is an str;
+ for example ``' 33 \t '`` is allowed and the resulting value
+ is ``TipoDteEnum(33)``.
+
+ Implementation almost identical to
+ :class:`cl_sii.extras.mm_fields.RutField`.
+
+ """
+
+ default_error_messages = {
+ 'invalid': 'Not a valid Tipo DTE.'
+ }
+
+ def _serialize(self, value: Optional[object], attr: str, obj: object) -> Optional[int]:
+ validated: Optional[TipoDteEnum] = self._validated(value)
+ return validated.value if validated is not None else None
+
+ def _deserialize(self, value: object, attr: str, data: dict) -> Optional[TipoDteEnum]:
+ return self._validated(value)
+
+ def _validated(self, value: Optional[object]) -> Optional[TipoDteEnum]:
+ if value is None or isinstance(value, TipoDteEnum):
+ validated = value
+ else:
+ if isinstance(value, bool):
+ # is value is bool, `isinstance(value, int)` is True and `int(value)` works!
+ self.fail('type')
+ try:
+ value = int(value) # type: ignore
+ except ValueError:
+ # `int('x')` raises 'ValueError', not 'TypeError'
+ self.fail('type')
+ except TypeError:
+ # `int(date(2018, 10, 10))` raises 'TypeError', unlike `int('x')`
+ self.fail('type')
+
+ try:
+ validated = TipoDteEnum(value) # type: ignore
+ except ValueError:
+ # TipoDteEnum('x') raises 'ValueError', not 'TypeError'
+ self.fail('invalid')
+ return validated
diff --git a/cl_sii/libs/xml_utils.py b/cl_sii/libs/xml_utils.py
index 1a9fd0a3..83a9d69e 100644
--- a/cl_sii/libs/xml_utils.py
+++ b/cl_sii/libs/xml_utils.py
@@ -1,5 +1,6 @@
import logging
import os
+from typing import IO
import defusedxml
import defusedxml.lxml
@@ -237,3 +238,45 @@ def validate_xml_doc(xml_schema: lxml.etree.XMLSchema, xml_doc: lxml.etree.Eleme
validation_error_msg = str(exc)
raise XmlSchemaDocValidationError(validation_error_msg) from exc
+
+
+def write_xml_doc(xml_doc: lxml.etree.ElementBase, output: IO[bytes]) -> None:
+ """
+ Write ``xml_doc`` to bytes stream ``output``.
+
+ In this context, "write" means "serialize", so there are a number of
+ observations on that regard:
+
+ * Encoding will be preserved.
+ * XML declaration (````) will be included.
+ * Quoting of each XML declaration attribute's value may change
+ i.e. from ``"`` to ``'`` or viceversa.
+ * In self-closing tags, the whitespace between the last attribute
+ and the closing (``/>``) may be removed e.g.
+ ```` to
+ ````
+ * No pretty-print.
+
+ For a temporary bytes stream in memory you may create a
+ :class:`io.BytesIO` object.
+
+ """
+ # note: use `IO[X]` for arguments and `TextIO`/`BinaryIO` for return types (says GVR).
+ # https://github.com/python/typing/issues/518#issuecomment-350903120
+
+ xml_etree: lxml.etree.ElementTree = xml_doc.getroottree()
+
+ # See:
+ # https://lxml.de/api/lxml.etree._ElementTree-class.html#write
+ xml_etree.write(
+ file=output,
+ encoding=xml_etree.docinfo.encoding,
+ # alternatives: 'xml', 'html', 'text' or 'c14n'
+ method='xml',
+ # note: include XML declaration (``).
+ xml_declaration=True,
+ pretty_print=False,
+ # note: we are not sure what this does.
+ # default: True.
+ with_tail=True,
+ )
diff --git a/cl_sii/rut/constants.py b/cl_sii/rut/constants.py
index 4bf379fb..cb5afd5d 100644
--- a/cl_sii/rut/constants.py
+++ b/cl_sii/rut/constants.py
@@ -2,7 +2,7 @@
RUT-related constants.
Source: XML type 'RUTType' in official schema 'SiiTypes_v10.xsd'.
-https://github.com/fyndata/lib-cl-sii-python/blob/8b51350/cl_sii/data/ref/factura_electronica/schema_dte/SiiTypes_v10.xsd#L127-L136
+https://github.com/fyndata/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd#L127-L136
"""
import re
diff --git a/scripts/clean_dte_xml_file.py b/scripts/clean_dte_xml_file.py
new file mode 100755
index 00000000..c6880d45
--- /dev/null
+++ b/scripts/clean_dte_xml_file.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+"""
+Clean DTE XML files.
+
+
+Example for a single file::
+
+ ./scripts/clean_dte_xml_file.py file \
+ 'tests/test_data/sii-dte/DTE--76354771-K--33--170.xml' \
+ 'tests/test_data/sii-dte/DTE--76354771-K--33--170-clean.xml'
+
+
+Example for all files in a directory::
+
+ ./scripts/clean_dte_xml_file.py dir 'tests/test_data/sii-dte/'
+
+
+"""
+import difflib
+import os
+import pathlib
+import sys
+from typing import Iterable
+
+try:
+ import cl_sii # noqa: F401
+except ImportError:
+ # If package 'cl-sii' is not installed, try appending the project repo directory to the
+ # Python path, assuming thath we are in the project repo. If not, it will fail nonetheless.
+ sys.path.append(os.path.dirname(os.path.abspath(__name__)))
+ import cl_sii # noqa: F401
+
+import cl_sii.dte.parse
+from cl_sii.libs import xml_utils
+
+
+# TODO: log messages instead of print.
+
+
+def clean_dte_xml_file(input_file_path: str, output_file_path: str) -> Iterable[bytes]:
+ with open(input_file_path, mode='rb') as f:
+ file_bytes = f.read()
+
+ xml_doc = xml_utils.parse_untrusted_xml(file_bytes)
+
+ xml_doc_cleaned, modified = cl_sii.dte.parse.clean_dte_xml(
+ xml_doc,
+ set_missing_xmlns=True,
+ remove_doc_personalizado=True,
+ )
+
+ cl_sii.dte.parse.validate_dte_xml(xml_doc_cleaned)
+
+ with open(output_file_path, 'w+b') as f:
+ xml_utils.write_xml_doc(xml_doc_cleaned, f)
+
+ with open(output_file_path, mode='rb') as f:
+ file_bytes_rewritten = f.read()
+
+ # note: another way to compute the difference in a similar format is
+ # `diff -Naur $input_file_path $output_file_path`
+ file_bytes_diff_gen = difflib.diff_bytes(
+ dfunc=difflib.unified_diff,
+ a=file_bytes.splitlines(),
+ b=file_bytes_rewritten.splitlines())
+
+ return file_bytes_diff_gen
+
+
+def main_single_file(input_file_path: str, output_file_path: str) -> None:
+ file_bytes_diff_gen = clean_dte_xml_file(
+ input_file_path=input_file_path,
+ output_file_path=output_file_path)
+
+ for diff_line in file_bytes_diff_gen:
+ print(diff_line)
+
+
+def main_dir_files(input_files_dir_path: str) -> None:
+ for p in pathlib.Path(input_files_dir_path).iterdir():
+ if not p.is_file():
+ continue
+
+ # e.g. 'an example.xml' -> 'an example.clean.xml'
+ input_file_path = str(p)
+ output_file_path = str(p.with_suffix(f'.clean{p.suffix}'))
+
+ print(f"\n\nWill clean file '{input_file_path}' and save it to '{output_file_path}'.")
+ file_bytes_diff_gen = clean_dte_xml_file(
+ input_file_path=input_file_path,
+ output_file_path=output_file_path)
+
+ print(f"Difference between input and output files:")
+ diff_line = None
+ for diff_line in file_bytes_diff_gen:
+ print(diff_line)
+ if diff_line is None:
+ print(f"No difference.")
+
+
+if __name__ == '__main__':
+ if sys.argv[1] == 'file':
+ main_single_file(
+ input_file_path=sys.argv[2],
+ output_file_path=sys.argv[3])
+ elif sys.argv[1] == 'dir':
+ main_dir_files(
+ input_files_dir_path=sys.argv[2])
+ else:
+ raise ValueError(f"Invalid option: '{sys.argv[1]}'")
diff --git a/scripts/example.py b/scripts/example.py
new file mode 100755
index 00000000..48644f3f
--- /dev/null
+++ b/scripts/example.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+"""
+Example script
+==============
+
+
+Example::
+
+ ./scripts/example.py arg1 arg2 arg3
+
+"""
+import os
+import sys
+from typing import Sequence
+
+try:
+ import cl_sii # noqa: F401
+except ImportError:
+ # If package 'cl-sii' is not installed, try appending the project repo directory to the
+ # Python path, assuming thath we are in the project repo. If not, it will fail nonetheless.
+ sys.path.append(os.path.dirname(os.path.abspath(__name__)))
+ import cl_sii # noqa: F401
+
+
+def main(args: Sequence[str]) -> None:
+ print("Example script.")
+ print(f"Args: {args!s}")
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/setup.py b/setup.py
index da24544f..d8cd39da 100755
--- a/setup.py
+++ b/setup.py
@@ -55,8 +55,7 @@ def get_version(*file_paths: Sequence[str]) -> str:
# Indicates that the "typing information" of the package should be distributed.
'py.typed',
# Data files that are not in a sub-package.
- 'data/ref/factura_electronica/schema_dte/*.xsd',
- 'data/ref/factura_electronica/schema_iecv/*.xsd',
+ 'data/ref/factura_electronica/schemas-xml/*.xsd',
],
}
diff --git a/tests/test_data/sii-dte/DTE--76354771-K--33--170.xml b/tests/test_data/sii-dte/DTE--76354771-K--33--170.xml
new file mode 100644
index 00000000..71ddbe58
--- /dev/null
+++ b/tests/test_data/sii-dte/DTE--76354771-K--33--170.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+ 33
+ 170
+ 2019-04-01
+ 1
+ 1
+ 2
+
+
+ 76354771-K
+ INGENIERIA ENACON SPA
+ Ingenieria y Construccion
+ ENACONLTDA@GMAIL.COM
+ 421000
+ 078525666
+ MERCED 753 16 ARBOLEDA DE QUIILOTA
+ QUILLOTA
+ QUILLOTA
+
+
+ 96790240-3
+ MINERA LOS PELAMBRES
+ EXTRACCION Y PROCESAMIENTO DE COBRE
+ Felipe Barria
+ Av. Apoquindo 4001 1802
+ LAS CONDES
+ SANTIAGO
+
+
+ 2517900
+ 19.00
+ 478401
+ 2996301
+
+
+
+ 1
+ Tableros electricos 3 tom
+ as 3p + t; 380v; 50 hz; 32a; 3 tomas monofasicas 2p + t; 240v; 50 hz; 16a; proteccion ip, segun orden de compra de la referencia.-
+ 2.00
+ Unid
+ 1258950.00
+ 2517900
+
+
+ 1
+ 801
+ 4510083633
+ 2019-03-22
+
+76354771-K| 33 | 1702019-04-0196790240-3MINERA LOS PELAMBRES2996301Tableros electricos 3 tom76354771-KINGENIERIA ENACON SPA33 | 1701702019-04-01uv7BUO3yg/7RoMjh1mPXXG/8YIwjtXsu7kcOq7dZQj66QCiY4FVz2fIhF1jaU0GSikq/jq26IFGylGus92OnPQ==Aw==300PI7bw8y0RNUJrGxyhb2gr6BjFtv/Ikyo/6g69wycoXTHSoRML3xvZvOBytreN7REw9JF0Ldoj91RRtaZbH38bA==2019-04-01T01:36:40DKFS7bNYRpVYLNEII+eyLcBHmNwQIHVkbqgR96wKcnDEcU6NsHQUMUyXpr7ql7xD9iuGkZDmNxHuY+Mq913oSA==
+ 2019-04-01T01:36:40
+
+
+
+
+
+
+
+
+
+
+
+ij2Qn6xOc2eRx3hwyO/GrzptoBk=
+
+
+
+fsYP5p/lNfofAz8POShrJjqXdBTNNtvv4/TWCxbvwTIAXr7BLrlvX3C/Hpfo4viqaxSu1OGFgPnk
+ddDIFwj/ZsVdbdB+MhpKkyha83RxhJpYBVBY3c+y9J6oMfdIdMAYXhEkFw8w63KHyhdf2E9dnbKi
+wqSxDcYjTT6vXsLPrZk=
+
+
+
+
+
+pB4Bs0Op+L0za/zpFQYBiCrVlIOKgULo4uvRLCI5picuxI6X4rE7f3g9XBIZrqtmTUSshmifKLXl
+9T/ScdkuLyIcsHj0QHkbe0LCHSzw1+pH1yTT/dn5NeFVR2InIkL/PzHkjmVJR/M0R50lGJ1W+nqN
+Uavs/9J+gR9BBMs/eYE=
+
+AQAB
+
+
+
+
+MIIGVDCCBTygAwIBAgIKMUWmvgAAAAjUHTANBgkqhkiG9w0BAQUFADCB0jELMAkGA1UEBhMCQ0wx
+HTAbBgNVBAgTFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQHEwhTYW50aWFnbzEUMBIGA1UE
+ChMLRS1DRVJUQ0hJTEUxIDAeBgNVBAsTF0F1dG9yaWRhZCBDZXJ0aWZpY2Fkb3JhMTAwLgYDVQQD
+EydFLUNFUlRDSElMRSBDQSBGSVJNQSBFTEVDVFJPTklDQSBTSU1QTEUxJzAlBgkqhkiG9w0BCQEW
+GHNjbGllbnRlc0BlLWNlcnRjaGlsZS5jbDAeFw0xNzA5MDQyMTExMTJaFw0yMDA5MDMyMTExMTJa
+MIHXMQswCQYDVQQGEwJDTDEUMBIGA1UECBMLVkFMUEFSQUlTTyAxETAPBgNVBAcTCFF1aWxsb3Rh
+MS8wLQYDVQQKEyZTZXJ2aWNpb3MgQm9uaWxsYSB5IExvcGV6IHkgQ2lhLiBMdGRhLjEkMCIGA1UE
+CwwbSW5nZW5pZXLDrWEgeSBDb25zdHJ1Y2Npw7NuMSMwIQYDVQQDExpSYW1vbiBodW1iZXJ0byBM
+b3BleiAgSmFyYTEjMCEGCSqGSIb3DQEJARYUZW5hY29ubHRkYUBnbWFpbC5jb20wgZ8wDQYJKoZI
+hvcNAQEBBQADgY0AMIGJAoGBAKQeAbNDqfi9M2v86RUGAYgq1ZSDioFC6OLr0SwiOaYnLsSOl+Kx
+O394PVwSGa6rZk1ErIZonyi15fU/0nHZLi8iHLB49EB5G3tCwh0s8NfqR9ck0/3Z+TXhVUdiJyJC
+/z8x5I5lSUfzNEedJRidVvp6jVGr7P/SfoEfQQTLP3mBAgMBAAGjggKnMIICozA9BgkrBgEEAYI3
+FQcEMDAuBiYrBgEEAYI3FQiC3IMvhZOMZoXVnReC4twnge/sPGGBy54UhqiCWAIBZAIBBDAdBgNV
+HQ4EFgQU1dVHhF0UVe7RXIz4cjl3/Vew+qowCwYDVR0PBAQDAgTwMB8GA1UdIwQYMBaAFHjhPp/S
+ErN6PI3NMA5Ts0MpB7NVMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9jcmwuZS1jZXJ0Y2hpbGUu
+Y2wvZWNlcnRjaGlsZWNhRkVTLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0dHA6
+Ly9vY3NwLmVjZXJ0Y2hpbGUuY2wvb2NzcDAjBgNVHREEHDAaoBgGCCsGAQQBwQEBoAwWCjEzMTg1
+MDk1LTYwIwYDVR0SBBwwGqAYBggrBgEEAcEBAqAMFgo5NjkyODE4MC01MIIBTQYDVR0gBIIBRDCC
+AUAwggE8BggrBgEEAcNSBTCCAS4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cuZS1jZXJ0Y2hpbGUu
+Y2wvQ1BTLmh0bTCB/AYIKwYBBQUHAgIwge8egewAQwBlAHIAdABpAGYAaQBjAGEAZABvACAARgBp
+AHIAbQBhACAAUwBpAG0AcABsAGUALgAgAEgAYQAgAHMAaQBkAG8AIAB2AGEAbABpAGQAYQBkAG8A
+IABlAG4AIABmAG8AcgBtAGEAIABwAHIAZQBzAGUAbgBjAGkAYQBsACwAIABxAHUAZQBkAGEAbgBk
+AG8AIABoAGEAYgBpAGwAaQB0AGEAZABvACAAZQBsACAAQwBlAHIAdABpAGYAaQBjAGEAZABvACAA
+cABhAHIAYQAgAHUAcwBvACAAdAByAGkAYgB1AHQAYQByAGkAbzANBgkqhkiG9w0BAQUFAAOCAQEA
+mxtPpXWslwI0+uJbyuS9s/S3/Vs0imn758xMU8t4BHUd+OlMdNAMQI1G2+q/OugdLQ/a9Sg3clKD
+qXR4lHGl8d/Yq4yoJzDD3Ceez8qenY3JwGUhPzw9oDpg4mXWvxQDXSFeW/u/BgdadhfGnpwx61Un
++/fU24ZgU1dDJ4GKj5oIPHUIjmoSBhnstEhIr6GJWSTcDKTyzRdqBlaVhenH2Qs6Mw6FrOvRPuud
+B7lo1+OgxMb/Gjyu6XnEaPu7Vq4XlLYMoCD2xrV7WEADaDTm7KcNLczVAYqWSF1WUqYSxmPoQDFY
++kMTThJyCXBlE0NADInrkwWgLLygkKI7zXkwaw==
+
+
+
+
diff --git a/tests/test_data/sii-rtc/AEC--76354771-K--33---170--SEQ-2.xml b/tests/test_data/sii-rtc/AEC--76354771-K--33---170--SEQ-2.xml
new file mode 100644
index 00000000..ff2cca48
--- /dev/null
+++ b/tests/test_data/sii-rtc/AEC--76354771-K--33---170--SEQ-2.xml
@@ -0,0 +1,293 @@
+
+
+
+
+
+ 76389992-6
+ 76598556-0
+ ST Capital Servicios Financieros
+ APrat@Financiaenlinea.com
+ 2019-04-05T12:57:32
+
+
+
+
+
+
+
+
+
+
+ 33
+ 170
+ 2019-04-01
+ 1
+ 1
+ 2
+
+
+ 76354771-K
+ INGENIERIA ENACON SPA
+ Ingenieria y Construccion
+ ENACONLTDA@GMAIL.COM
+ 421000
+ 078525666
+ MERCED 753 16 ARBOLEDA DE QUIILOTA
+ QUILLOTA
+ QUILLOTA
+
+
+ 96790240-3
+ MINERA LOS PELAMBRES
+ EXTRACCION Y PROCESAMIENTO DE COBRE
+ Felipe Barria
+ Av. Apoquindo 4001 1802
+ LAS CONDES
+ SANTIAGO
+
+
+ 2517900
+ 19.00
+ 478401
+ 2996301
+
+
+
+ 1
+ Tableros electricos 3 tom
+ as 3p + t; 380v; 50 hz; 32a; 3 tomas monofasicas 2p + t; 240v; 50 hz; 16a; proteccion ip, segun orden de compra de la referencia.-
+ 2.00
+ Unid
+ 1258950.00
+ 2517900
+
+
+ 1
+ 801
+ 4510083633
+ 2019-03-22
+
+76354771-K33 | 1702019-04-0196790240-3MINERA LOS PELAMBRES2996301Tableros electricos 3 tom76354771-KINGENIERIA ENACON SPA33 | 1701702019-04-01uv7BUO3yg/7RoMjh1mPXXG/8YIwjtXsu7kcOq7dZQj66QCiY4FVz2fIhF1jaU0GSikq/jq26IFGylGus92OnPQ==Aw==300PI7bw8y0RNUJrGxyhb2gr6BjFtv/Ikyo/6g69wycoXTHSoRML3xvZvOBytreN7REw9JF0Ldoj91RRtaZbH38bA==2019-04-01T01:36:40DKFS7bNYRpVYLNEII+eyLcBHmNwQIHVkbqgR96wKcnDEcU6NsHQUMUyXpr7ql7xD9iuGkZDmNxHuY+Mq913oSA==
+ 2019-04-01T01:36:40
+
+
+
+
+
+
+
+
+
+
+
+ij2Qn6xOc2eRx3hwyO/GrzptoBk=
+
+
+
+fsYP5p/lNfofAz8POShrJjqXdBTNNtvv4/TWCxbvwTIAXr7BLrlvX3C/Hpfo4viqaxSu1OGFgPnk
+ddDIFwj/ZsVdbdB+MhpKkyha83RxhJpYBVBY3c+y9J6oMfdIdMAYXhEkFw8w63KHyhdf2E9dnbKi
+wqSxDcYjTT6vXsLPrZk=
+
+
+
+
+
+pB4Bs0Op+L0za/zpFQYBiCrVlIOKgULo4uvRLCI5picuxI6X4rE7f3g9XBIZrqtmTUSshmifKLXl
+9T/ScdkuLyIcsHj0QHkbe0LCHSzw1+pH1yTT/dn5NeFVR2InIkL/PzHkjmVJR/M0R50lGJ1W+nqN
+Uavs/9J+gR9BBMs/eYE=
+
+AQAB
+
+
+
+
+MIIGVDCCBTygAwIBAgIKMUWmvgAAAAjUHTANBgkqhkiG9w0BAQUFADCB0jELMAkGA1UEBhMCQ0wx
+HTAbBgNVBAgTFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQHEwhTYW50aWFnbzEUMBIGA1UE
+ChMLRS1DRVJUQ0hJTEUxIDAeBgNVBAsTF0F1dG9yaWRhZCBDZXJ0aWZpY2Fkb3JhMTAwLgYDVQQD
+EydFLUNFUlRDSElMRSBDQSBGSVJNQSBFTEVDVFJPTklDQSBTSU1QTEUxJzAlBgkqhkiG9w0BCQEW
+GHNjbGllbnRlc0BlLWNlcnRjaGlsZS5jbDAeFw0xNzA5MDQyMTExMTJaFw0yMDA5MDMyMTExMTJa
+MIHXMQswCQYDVQQGEwJDTDEUMBIGA1UECBMLVkFMUEFSQUlTTyAxETAPBgNVBAcTCFF1aWxsb3Rh
+MS8wLQYDVQQKEyZTZXJ2aWNpb3MgQm9uaWxsYSB5IExvcGV6IHkgQ2lhLiBMdGRhLjEkMCIGA1UE
+CwwbSW5nZW5pZXLDrWEgeSBDb25zdHJ1Y2Npw7NuMSMwIQYDVQQDExpSYW1vbiBodW1iZXJ0byBM
+b3BleiAgSmFyYTEjMCEGCSqGSIb3DQEJARYUZW5hY29ubHRkYUBnbWFpbC5jb20wgZ8wDQYJKoZI
+hvcNAQEBBQADgY0AMIGJAoGBAKQeAbNDqfi9M2v86RUGAYgq1ZSDioFC6OLr0SwiOaYnLsSOl+Kx
+O394PVwSGa6rZk1ErIZonyi15fU/0nHZLi8iHLB49EB5G3tCwh0s8NfqR9ck0/3Z+TXhVUdiJyJC
+/z8x5I5lSUfzNEedJRidVvp6jVGr7P/SfoEfQQTLP3mBAgMBAAGjggKnMIICozA9BgkrBgEEAYI3
+FQcEMDAuBiYrBgEEAYI3FQiC3IMvhZOMZoXVnReC4twnge/sPGGBy54UhqiCWAIBZAIBBDAdBgNV
+HQ4EFgQU1dVHhF0UVe7RXIz4cjl3/Vew+qowCwYDVR0PBAQDAgTwMB8GA1UdIwQYMBaAFHjhPp/S
+ErN6PI3NMA5Ts0MpB7NVMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9jcmwuZS1jZXJ0Y2hpbGUu
+Y2wvZWNlcnRjaGlsZWNhRkVTLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0dHA6
+Ly9vY3NwLmVjZXJ0Y2hpbGUuY2wvb2NzcDAjBgNVHREEHDAaoBgGCCsGAQQBwQEBoAwWCjEzMTg1
+MDk1LTYwIwYDVR0SBBwwGqAYBggrBgEEAcEBAqAMFgo5NjkyODE4MC01MIIBTQYDVR0gBIIBRDCC
+AUAwggE8BggrBgEEAcNSBTCCAS4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cuZS1jZXJ0Y2hpbGUu
+Y2wvQ1BTLmh0bTCB/AYIKwYBBQUHAgIwge8egewAQwBlAHIAdABpAGYAaQBjAGEAZABvACAARgBp
+AHIAbQBhACAAUwBpAG0AcABsAGUALgAgAEgAYQAgAHMAaQBkAG8AIAB2AGEAbABpAGQAYQBkAG8A
+IABlAG4AIABmAG8AcgBtAGEAIABwAHIAZQBzAGUAbgBjAGkAYQBsACwAIABxAHUAZQBkAGEAbgBk
+AG8AIABoAGEAYgBpAGwAaQB0AGEAZABvACAAZQBsACAAQwBlAHIAdABpAGYAaQBjAGEAZABvACAA
+cABhAHIAYQAgAHUAcwBvACAAdAByAGkAYgB1AHQAYQByAGkAbzANBgkqhkiG9w0BAQUFAAOCAQEA
+mxtPpXWslwI0+uJbyuS9s/S3/Vs0imn758xMU8t4BHUd+OlMdNAMQI1G2+q/OugdLQ/a9Sg3clKD
+qXR4lHGl8d/Yq4yoJzDD3Ceez8qenY3JwGUhPzw9oDpg4mXWvxQDXSFeW/u/BgdadhfGnpwx61Un
++/fU24ZgU1dDJ4GKj5oIPHUIjmoSBhnstEhIr6GJWSTcDKTyzRdqBlaVhenH2Qs6Mw6FrOvRPuud
+B7lo1+OgxMb/Gjyu6XnEaPu7Vq4XlLYMoCD2xrV7WEADaDTm7KcNLczVAYqWSF1WUqYSxmPoQDFY
++kMTThJyCXBlE0NADInrkwWgLLygkKI7zXkwaw==
+
+
+
+2019-04-01T10:22:02Sbpc3eOcmYV+eaXyipjlsR3wQoI=
+XYnmhn2vYx+mz74rpzO2OnaF8ZrXdRMCS7UVbvRMnUt+VFuED5FYhWWNX0IQVffN
+aqmksqhmRpKQksc5p9FcNqFswwBL4lOwQ0arom3QkQUrryv2CDxIkAI3Tr5vDiks
+kQtCjIAuBBjElAlEe0p9MCnOlxgGKJYtFrHd75FS2QE=
+
+pB4Bs0Op+L0za/zpFQYBiCrVlIOKgULo4uvRLCI5picuxI6X4rE7f3g9XBIZrqtm
+TUSshmifKLXl9T/ScdkuLyIcsHj0QHkbe0LCHSzw1+pH1yTT/dn5NeFVR2InIkL/
+PzHkjmVJR/M0R50lGJ1W+nqNUavs/9J+gR9BBMs/eYE=
+AQAB
+MIIGVDCCBTygAwIBAgIKMUWmvgAAAAjUHTANBgkqhkiG9w0BAQUFADCB0jELMAkG
+A1UEBhMCQ0wxHTAbBgNVBAgTFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQH
+EwhTYW50aWFnbzEUMBIGA1UEChMLRS1DRVJUQ0hJTEUxIDAeBgNVBAsTF0F1dG9y
+aWRhZCBDZXJ0aWZpY2Fkb3JhMTAwLgYDVQQDEydFLUNFUlRDSElMRSBDQSBGSVJN
+QSBFTEVDVFJPTklDQSBTSU1QTEUxJzAlBgkqhkiG9w0BCQEWGHNjbGllbnRlc0Bl
+LWNlcnRjaGlsZS5jbDAeFw0xNzA5MDQyMTExMTJaFw0yMDA5MDMyMTExMTJaMIHX
+MQswCQYDVQQGEwJDTDEUMBIGA1UECBMLVkFMUEFSQUlTTyAxETAPBgNVBAcTCFF1
+aWxsb3RhMS8wLQYDVQQKEyZTZXJ2aWNpb3MgQm9uaWxsYSB5IExvcGV6IHkgQ2lh
+LiBMdGRhLjEkMCIGA1UECwwbSW5nZW5pZXLDrWEgeSBDb25zdHJ1Y2Npw7NuMSMw
+IQYDVQQDExpSYW1vbiBodW1iZXJ0byBMb3BleiAgSmFyYTEjMCEGCSqGSIb3DQEJ
+ARYUZW5hY29ubHRkYUBnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
+AoGBAKQeAbNDqfi9M2v86RUGAYgq1ZSDioFC6OLr0SwiOaYnLsSOl+KxO394PVwS
+Ga6rZk1ErIZonyi15fU/0nHZLi8iHLB49EB5G3tCwh0s8NfqR9ck0/3Z+TXhVUdi
+JyJC/z8x5I5lSUfzNEedJRidVvp6jVGr7P/SfoEfQQTLP3mBAgMBAAGjggKnMIIC
+ozA9BgkrBgEEAYI3FQcEMDAuBiYrBgEEAYI3FQiC3IMvhZOMZoXVnReC4twnge/s
+PGGBy54UhqiCWAIBZAIBBDAdBgNVHQ4EFgQU1dVHhF0UVe7RXIz4cjl3/Vew+qow
+CwYDVR0PBAQDAgTwMB8GA1UdIwQYMBaAFHjhPp/SErN6PI3NMA5Ts0MpB7NVMD4G
+A1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9jcmwuZS1jZXJ0Y2hpbGUuY2wvZWNlcnRj
+aGlsZWNhRkVTLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0dHA6
+Ly9vY3NwLmVjZXJ0Y2hpbGUuY2wvb2NzcDAjBgNVHREEHDAaoBgGCCsGAQQBwQEB
+oAwWCjEzMTg1MDk1LTYwIwYDVR0SBBwwGqAYBggrBgEEAcEBAqAMFgo5NjkyODE4
+MC01MIIBTQYDVR0gBIIBRDCCAUAwggE8BggrBgEEAcNSBTCCAS4wLQYIKwYBBQUH
+AgEWIWh0dHA6Ly93d3cuZS1jZXJ0Y2hpbGUuY2wvQ1BTLmh0bTCB/AYIKwYBBQUH
+AgIwge8egewAQwBlAHIAdABpAGYAaQBjAGEAZABvACAARgBpAHIAbQBhACAAUwBp
+AG0AcABsAGUALgAgAEgAYQAgAHMAaQBkAG8AIAB2AGEAbABpAGQAYQBkAG8AIABl
+AG4AIABmAG8AcgBtAGEAIABwAHIAZQBzAGUAbgBjAGkAYQBsACwAIABxAHUAZQBk
+AGEAbgBkAG8AIABoAGEAYgBpAGwAaQB0AGEAZABvACAAZQBsACAAQwBlAHIAdABp
+AGYAaQBjAGEAZABvACAAcABhAHIAYQAgAHUAcwBvACAAdAByAGkAYgB1AHQAYQBy
+AGkAbzANBgkqhkiG9w0BAQUFAAOCAQEAmxtPpXWslwI0+uJbyuS9s/S3/Vs0imn7
+58xMU8t4BHUd+OlMdNAMQI1G2+q/OugdLQ/a9Sg3clKDqXR4lHGl8d/Yq4yoJzDD
+3Ceez8qenY3JwGUhPzw9oDpg4mXWvxQDXSFeW/u/BgdadhfGnpwx61Un+/fU24Zg
+U1dDJ4GKj5oIPHUIjmoSBhnstEhIr6GJWSTcDKTyzRdqBlaVhenH2Qs6Mw6FrOvR
+PuudB7lo1+OgxMb/Gjyu6XnEaPu7Vq4XlLYMoCD2xrV7WEADaDTm7KcNLczVAYqW
+SF1WUqYSxmPoQDFY+kMTThJyCXBlE0NADInrkwWgLLygkKI7zXkwaw==
+
+
+
+
+ 1
+
+ 33
+ 76354771-K
+ 96790240-3
+ 170
+ 2019-04-01
+ 2996301
+
+
+ 76354771-K
+ SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIMITADA
+ MERCED 753 16 ARBOLEDA DE QUIILOTA
+ enaconltda@gmail.com
+
+ 76354771-K
+ SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIM
+
+ Se declara bajo juramento que SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIMITADA, RUT 76354771-K ha puesto a disposición del cesionario ST CAPITAL S.A., RUT 76389992-6, el o los documentos donde constan los recibos de las mercaderías entregadas o servicios prestados, entregados por parte del deudor de la factura MINERA LOS PELAMBRES, RUT 96790240-3, deacuerdo a lo establecido en la Ley N°19.983.
+
+
+ 76389992-6
+ ST CAPITAL S.A.
+ Isidora Goyenechea 2939 Oficina 602
+ fynpal-app-notif-st-capital@fynpal.com
+
+ 2996301
+ 2019-05-01
+ 2019-04-01T10:22:02
+ sR0uKcx6gaEYEJ3ALOeKAvW2fME=
+aAQwo+xJc+ylMjjq6ksusVmNSwLbQDDItLc0lYQKBrMzoP2ZqsyFkIuXDt0hfLm0
+MhJpB6PSWgfmeCvD1zYfs6VMaeTYpwLLAEHLagJBte99PFKr065+sQow8Z+LlCNK
+0jy+CexRKGwOeJNLjBMkiUL31TrhnuKgLfO6cEA3TGk=
+
+pB4Bs0Op+L0za/zpFQYBiCrVlIOKgULo4uvRLCI5picuxI6X4rE7f3g9XBIZrqtm
+TUSshmifKLXl9T/ScdkuLyIcsHj0QHkbe0LCHSzw1+pH1yTT/dn5NeFVR2InIkL/
+PzHkjmVJR/M0R50lGJ1W+nqNUavs/9J+gR9BBMs/eYE=
+AQAB
+MIIGVDCCBTygAwIBAgIKMUWmvgAAAAjUHTANBgkqhkiG9w0BAQUFADCB0jELMAkG
+A1UEBhMCQ0wxHTAbBgNVBAgTFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQH
+EwhTYW50aWFnbzEUMBIGA1UEChMLRS1DRVJUQ0hJTEUxIDAeBgNVBAsTF0F1dG9y
+aWRhZCBDZXJ0aWZpY2Fkb3JhMTAwLgYDVQQDEydFLUNFUlRDSElMRSBDQSBGSVJN
+QSBFTEVDVFJPTklDQSBTSU1QTEUxJzAlBgkqhkiG9w0BCQEWGHNjbGllbnRlc0Bl
+LWNlcnRjaGlsZS5jbDAeFw0xNzA5MDQyMTExMTJaFw0yMDA5MDMyMTExMTJaMIHX
+MQswCQYDVQQGEwJDTDEUMBIGA1UECBMLVkFMUEFSQUlTTyAxETAPBgNVBAcTCFF1
+aWxsb3RhMS8wLQYDVQQKEyZTZXJ2aWNpb3MgQm9uaWxsYSB5IExvcGV6IHkgQ2lh
+LiBMdGRhLjEkMCIGA1UECwwbSW5nZW5pZXLDrWEgeSBDb25zdHJ1Y2Npw7NuMSMw
+IQYDVQQDExpSYW1vbiBodW1iZXJ0byBMb3BleiAgSmFyYTEjMCEGCSqGSIb3DQEJ
+ARYUZW5hY29ubHRkYUBnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
+AoGBAKQeAbNDqfi9M2v86RUGAYgq1ZSDioFC6OLr0SwiOaYnLsSOl+KxO394PVwS
+Ga6rZk1ErIZonyi15fU/0nHZLi8iHLB49EB5G3tCwh0s8NfqR9ck0/3Z+TXhVUdi
+JyJC/z8x5I5lSUfzNEedJRidVvp6jVGr7P/SfoEfQQTLP3mBAgMBAAGjggKnMIIC
+ozA9BgkrBgEEAYI3FQcEMDAuBiYrBgEEAYI3FQiC3IMvhZOMZoXVnReC4twnge/s
+PGGBy54UhqiCWAIBZAIBBDAdBgNVHQ4EFgQU1dVHhF0UVe7RXIz4cjl3/Vew+qow
+CwYDVR0PBAQDAgTwMB8GA1UdIwQYMBaAFHjhPp/SErN6PI3NMA5Ts0MpB7NVMD4G
+A1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9jcmwuZS1jZXJ0Y2hpbGUuY2wvZWNlcnRj
+aGlsZWNhRkVTLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0dHA6
+Ly9vY3NwLmVjZXJ0Y2hpbGUuY2wvb2NzcDAjBgNVHREEHDAaoBgGCCsGAQQBwQEB
+oAwWCjEzMTg1MDk1LTYwIwYDVR0SBBwwGqAYBggrBgEEAcEBAqAMFgo5NjkyODE4
+MC01MIIBTQYDVR0gBIIBRDCCAUAwggE8BggrBgEEAcNSBTCCAS4wLQYIKwYBBQUH
+AgEWIWh0dHA6Ly93d3cuZS1jZXJ0Y2hpbGUuY2wvQ1BTLmh0bTCB/AYIKwYBBQUH
+AgIwge8egewAQwBlAHIAdABpAGYAaQBjAGEAZABvACAARgBpAHIAbQBhACAAUwBp
+AG0AcABsAGUALgAgAEgAYQAgAHMAaQBkAG8AIAB2AGEAbABpAGQAYQBkAG8AIABl
+AG4AIABmAG8AcgBtAGEAIABwAHIAZQBzAGUAbgBjAGkAYQBsACwAIABxAHUAZQBk
+AGEAbgBkAG8AIABoAGEAYgBpAGwAaQB0AGEAZABvACAAZQBsACAAQwBlAHIAdABp
+AGYAaQBjAGEAZABvACAAcABhAHIAYQAgAHUAcwBvACAAdAByAGkAYgB1AHQAYQBy
+AGkAbzANBgkqhkiG9w0BAQUFAAOCAQEAmxtPpXWslwI0+uJbyuS9s/S3/Vs0imn7
+58xMU8t4BHUd+OlMdNAMQI1G2+q/OugdLQ/a9Sg3clKDqXR4lHGl8d/Yq4yoJzDD
+3Ceez8qenY3JwGUhPzw9oDpg4mXWvxQDXSFeW/u/BgdadhfGnpwx61Un+/fU24Zg
+U1dDJ4GKj5oIPHUIjmoSBhnstEhIr6GJWSTcDKTyzRdqBlaVhenH2Qs6Mw6FrOvR
+PuudB7lo1+OgxMb/Gjyu6XnEaPu7Vq4XlLYMoCD2xrV7WEADaDTm7KcNLczVAYqW
+SF1WUqYSxmPoQDFY+kMTThJyCXBlE0NADInrkwWgLLygkKI7zXkwaw==
+
+
+
+
+ 2
+
+ 33
+ 76354771-K
+ 96790240-3
+ 170
+ 2019-04-01
+ 2996301
+
+
+ 76389992-6
+ ST CAPITAL S.A.
+ Isidora Goyenechea 2939 Oficina 602
+ APrat@Financiaenlinea.com
+
+ 16360379-9
+ ANDRES PRATS VIAL
+
+ Se declara bajo juramento que ST CAPITAL S.A., RUT 76389992-6 ha puesto a disposicion del cesionario Fondo de Inversión Privado Deuda y Facturas, RUT 76598556-0, el documento validamente emitido al deudor MINERA LOS PELAMBRES, RUT 96790240-3.
+
+
+ 76598556-0
+ Fondo de Inversión Privado Deuda y Facturas
+ Arrayan 2750 Oficina 703 Providencia
+ solicitudes@stcapital.cl
+
+ 2996301
+ 2019-05-01
+ 2019-04-05T12:57:32
+
+nZflJb1JrHpc/LsqlogvyrQXQ9s=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
+
+
+
+ZeVB66oxAZfjbS8ls+4OkLu838g=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
\ No newline at end of file
diff --git a/tests/test_dte_parse.py b/tests/test_dte_parse.py
index 1e416018..fedb651b 100644
--- a/tests/test_dte_parse.py
+++ b/tests/test_dte_parse.py
@@ -1,12 +1,22 @@
+import difflib
+import io
import unittest
+from datetime import date
+
+import cl_sii.dte.constants
+from cl_sii.libs import xml_utils
+from cl_sii.rut import Rut
from cl_sii.dte.parse import ( # noqa: F401
clean_dte_xml, parse_dte_xml, validate_dte_xml,
- DTE_XML_SCHEMA_OBJ, DTE_XMLNS_MAP,
+ _remove_dte_xml_doc_personalizado, _set_dte_xml_missing_xmlns,
+ DTE_XML_SCHEMA_OBJ, DTE_XMLNS, DTE_XMLNS_MAP
)
+from .utils import read_test_file_bytes
+
-# TODO: add a real DTE XML file in 'tests/test_data/dte/'.
+_TEST_DTE_NEEDS_CLEAN_FILE_PATH = 'test_data/sii-dte/DTE--76354771-K--33--170.xml'
class OthersTest(unittest.TestCase):
@@ -15,11 +25,131 @@ def test_DTE_XML_SCHEMA_OBJ(self) -> None:
# TODO: implement
pass
+ def test_integration_ok(self) -> None:
+ # TODO: split in separate tests, with more coverage.
+
+ dte_bad_xml_file_path = _TEST_DTE_NEEDS_CLEAN_FILE_PATH
+
+ file_bytes = read_test_file_bytes(dte_bad_xml_file_path)
+ xml_doc = xml_utils.parse_untrusted_xml(file_bytes)
+
+ self.assertEqual(
+ xml_doc.getroottree().getroot().tag,
+ 'DTE')
+
+ with self.assertRaises(xml_utils.XmlSchemaDocValidationError) as cm:
+ validate_dte_xml(xml_doc)
+ self.assertSequenceEqual(
+ cm.exception.args,
+ ("Element 'DTE': No matching global declaration available for the validation root., "
+ "line 2", )
+ )
+ # This would raise:
+ # parse_dte_xml(xml_doc)
+
+ xml_doc_cleaned, modified = clean_dte_xml(
+ xml_doc,
+ set_missing_xmlns=True,
+ remove_doc_personalizado=True,
+ )
+ self.assertTrue(modified)
+
+ # This will not raise.
+ validate_dte_xml(xml_doc_cleaned)
+
+ self.assertEqual(
+ xml_doc_cleaned.getroottree().getroot().tag,
+ '{%s}DTE' % DTE_XMLNS)
+
+ f = io.BytesIO()
+ xml_utils.write_xml_doc(xml_doc_cleaned, f)
+ file_bytes_rewritten = f.getvalue()
+ del f
+
+ xml_doc_rewritten = xml_utils.parse_untrusted_xml(file_bytes_rewritten)
+ validate_dte_xml(xml_doc_rewritten)
+ parsed_dte_rewritten = parse_dte_xml(xml_doc_cleaned)
+
+ self.assertDictEqual(
+ dict(parsed_dte_rewritten.as_dict()),
+ dict(
+ emisor_rut=Rut('76354771-K'),
+ tipo_dte=cl_sii.dte.constants.TipoDteEnum.FACTURA_ELECTRONICA,
+ folio=170,
+ fecha_emision_date=date(2019, 4, 1),
+ receptor_rut=Rut('96790240-3'),
+ monto_total=2996301,
+ emisor_razon_social='INGENIERIA ENACON SPA',
+ receptor_razon_social='MINERA LOS PELAMBRES',
+ fecha_vencimiento_date=None,
+ ))
+
+ expected_file_bytes_diff = (
+ b'--- \n',
+ b'+++ \n',
+ b'@@ -1,5 +1,5 @@\n',
+ b'-',
+ b'-',
+ b"+",
+ b'+',
+ b' ',
+ b' ',
+ b' ',
+ b'@@ -59,13 +59,13 @@\n',
+ b' ',
+ b' ',
+ b' ',
+ b'-', # noqa: E501
+ b'-',
+ b'+', # noqa: E501
+ b'+',
+ b' ',
+ b' ',
+ b'-',
+ b'+',
+ b' ',
+ b'-',
+ b'+',
+ b' ij2Qn6xOc2eRx3hwyO/GrzptoBk=',
+ b' ',
+ b' ',
+ )
+
+ file_bytes_diff_gen = difflib.diff_bytes(
+ dfunc=difflib.unified_diff,
+ a=file_bytes.splitlines(),
+ b=file_bytes_rewritten.splitlines())
+ self.assertSequenceEqual(
+ [diff_line for diff_line in file_bytes_diff_gen],
+ expected_file_bytes_diff
+ )
+
class FunctionCleanDteXmlTest(unittest.TestCase):
- # TODO: implement
- pass
+ def test_clean_dte_xml_ok(self) -> None:
+ # TODO: implement
+ pass
+
+ def test_clean_dte_xml_fail(self) -> None:
+ # TODO: implement
+ pass
+
+ def test__set_dte_xml_missing_xmlns_ok(self) -> None:
+ # TODO: implement
+ pass
+
+ def test__set_dte_xml_missing_xmlns_fail(self) -> None:
+ # TODO: implement
+ pass
+
+ def test__remove_dte_xml_doc_personalizado_ok(self) -> None:
+ # TODO: implement
+ pass
+
+ def test__remove_dte_xml_doc_personalizado_fail(self) -> None:
+ # TODO: implement
+ pass
class FunctionParseDteXmlTest(unittest.TestCase):
diff --git a/tests/test_extras_mm_fields.py b/tests/test_extras_mm_fields.py
index a6266ec2..9253eefd 100644
--- a/tests/test_extras_mm_fields.py
+++ b/tests/test_extras_mm_fields.py
@@ -1,8 +1,9 @@
+from datetime import date
import unittest
import marshmallow
-from cl_sii.extras.mm_fields import Rut, RutField
+from cl_sii.extras.mm_fields import Rut, RutField, TipoDteEnum, TipoDteField
class RutFieldTest(unittest.TestCase):
@@ -144,3 +145,148 @@ def test_dump_fail(self) -> None:
data, errors = schema.dump(obj_invalid_3)
self.assertDictEqual(errors, {'emisor_rut': ['Not a syntactically valid RUT.']})
+
+
+class TipoDteFieldTest(unittest.TestCase):
+
+ def setUp(self) -> None:
+
+ class MyObj:
+ def __init__(self, tipo_dte: TipoDteEnum, other_field: int = None) -> None:
+ self.tipo_dte = tipo_dte
+ self.other_field = other_field
+
+ class MyBadObj:
+ def __init__(self, some_field: int) -> None:
+ self.some_field = some_field
+
+ class MyMmSchema(marshmallow.Schema):
+
+ class Meta:
+ strict = False
+
+ tipo_dte = TipoDteField(
+ required=True,
+ load_from='source field name',
+ )
+ other_field = marshmallow.fields.Integer(
+ required=False,
+ )
+
+ class MyMmSchemaStrict(marshmallow.Schema):
+
+ class Meta:
+ strict = True
+
+ tipo_dte = TipoDteField(
+ required=True,
+ load_from='source field name',
+ )
+ other_field = marshmallow.fields.Integer(
+ required=False,
+ )
+
+ self.MyObj = MyObj
+ self.MyBadObj = MyBadObj
+ self.MyMmSchema = MyMmSchema
+ self.MyMmSchemaStrict = MyMmSchemaStrict
+
+ def test_load_ok_valid(self) -> None:
+ schema = self.MyMmSchema()
+
+ data_valid_1 = {'source field name': 33}
+ data_valid_2 = {'source field name': TipoDteEnum(33)}
+ data_valid_3 = {'source field name': ' 33 \t '}
+
+ result = schema.load(data_valid_1)
+ self.assertDictEqual(dict(result.data), {'tipo_dte': TipoDteEnum(33)})
+ self.assertDictEqual(dict(result.errors), {})
+
+ result = schema.load(data_valid_2)
+ self.assertDictEqual(dict(result.data), {'tipo_dte': TipoDteEnum(33)})
+ self.assertDictEqual(dict(result.errors), {})
+
+ result = schema.load(data_valid_3)
+ self.assertDictEqual(dict(result.data), {'tipo_dte': TipoDteEnum(33)})
+ self.assertDictEqual(dict(result.errors), {})
+
+ def test_dump_ok_valid(self) -> None:
+ schema = self.MyMmSchema()
+
+ obj_valid_1 = self.MyObj(tipo_dte=TipoDteEnum(33))
+ obj_valid_2 = self.MyObj(tipo_dte=None)
+
+ data, errors = schema.dump(obj_valid_1)
+ self.assertDictEqual(data, {'tipo_dte': 33, 'other_field': None})
+ self.assertDictEqual(errors, {})
+
+ data, errors = schema.dump(obj_valid_2)
+ self.assertDictEqual(data, {'tipo_dte': None, 'other_field': None})
+ self.assertDictEqual(errors, {})
+
+ def test_dump_ok_strange(self) -> None:
+ # If the class of the object to be dumped has attributes that do not match at all the
+ # fields of the schema, there are no errors! Even if the schema has `strict = True` set.
+
+ schema = self.MyMmSchema()
+ schema_strict = self.MyMmSchemaStrict()
+
+ obj_valid_1 = self.MyBadObj(some_field=123)
+ obj_valid_2 = self.MyBadObj(some_field=None)
+
+ data, errors = schema.dump(obj_valid_1)
+ self.assertEqual((data, errors), ({}, {}))
+
+ data, errors = schema_strict.dump(obj_valid_1)
+ self.assertEqual((data, errors), ({}, {}))
+
+ data, errors = schema.dump(obj_valid_2)
+ self.assertEqual((data, errors), ({}, {}))
+
+ data, errors = schema_strict.dump(obj_valid_2)
+ self.assertEqual((data, errors), ({}, {}))
+
+ def test_load_fail(self) -> None:
+
+ schema = self.MyMmSchema()
+
+ data_invalid_1 = {'source field name': '123'}
+ data_invalid_2 = {'source field name': True}
+ data_invalid_3 = {'source field name': None}
+ data_invalid_4 = {}
+
+ result = schema.load(data_invalid_1)
+ self.assertDictEqual(dict(result.data), {})
+ self.assertDictEqual(dict(result.errors), {'source field name': ['Not a valid Tipo DTE.']})
+
+ result = schema.load(data_invalid_2)
+ self.assertDictEqual(dict(result.data), {})
+ self.assertDictEqual(dict(result.errors), {'source field name': ['Invalid input type.']})
+
+ result = schema.load(data_invalid_3)
+ self.assertDictEqual(dict(result.data), {})
+ self.assertDictEqual(dict(result.errors), {'source field name': ['Field may not be null.']})
+
+ result = schema.load(data_invalid_4)
+ self.assertDictEqual(dict(result.data), {})
+ self.assertDictEqual(dict(result.errors), {'source field name': ['Missing data for required field.']}) # noqa: E501
+
+ def test_dump_fail(self) -> None:
+ schema = self.MyMmSchema()
+
+ obj_invalid_1 = self.MyObj(tipo_dte=100)
+ obj_invalid_2 = self.MyObj(tipo_dte=True)
+ obj_invalid_3 = self.MyObj(tipo_dte='FACTURA_ELECTRONICA')
+ obj_invalid_4 = self.MyObj(tipo_dte='')
+ obj_invalid_5 = self.MyObj(tipo_dte=date(2018, 12, 23))
+
+ data, errors = schema.dump(obj_invalid_1)
+ self.assertDictEqual(errors, {'tipo_dte': ['Not a valid Tipo DTE.']})
+ data, errors = schema.dump(obj_invalid_2)
+ self.assertDictEqual(errors, {'tipo_dte': ['Invalid input type.']})
+ data, errors = schema.dump(obj_invalid_3)
+ self.assertDictEqual(errors, {'tipo_dte': ['Invalid input type.']})
+ data, errors = schema.dump(obj_invalid_4)
+ self.assertDictEqual(errors, {'tipo_dte': ['Invalid input type.']})
+ data, errors = schema.dump(obj_invalid_5)
+ self.assertDictEqual(errors, {'tipo_dte': ['Invalid input type.']})
diff --git a/tests/test_xml_utils.py b/tests/test_libs_xml_utils.py
similarity index 93%
rename from tests/test_xml_utils.py
rename to tests/test_libs_xml_utils.py
index bb04316e..a8a15723 100644
--- a/tests/test_xml_utils.py
+++ b/tests/test_libs_xml_utils.py
@@ -4,7 +4,7 @@
from cl_sii.libs.xml_utils import ( # noqa: F401
XmlSyntaxError, XmlFeatureForbidden,
- parse_untrusted_xml, read_xml_schema, validate_xml_doc,
+ parse_untrusted_xml, read_xml_schema, validate_xml_doc, write_xml_doc,
)
from .utils import read_test_file_bytes
@@ -99,3 +99,9 @@ class FunctionValidateXmlDocTest(unittest.TestCase):
# TODO: implement
pass
+
+
+class FunctionWriteXmlDocTest(unittest.TestCase):
+
+ # TODO: implement for function 'write_xml_doc'. Consider each of the "observations".
+ pass
diff --git a/tests/test_scripts_clean_dte_xml_file.py b/tests/test_scripts_clean_dte_xml_file.py
new file mode 100644
index 00000000..05e89749
--- /dev/null
+++ b/tests/test_scripts_clean_dte_xml_file.py
@@ -0,0 +1,2 @@
+
+# TODO: implement tests for script 'clean_dte_xml_file.py'