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-K331702019-04-0196790240-3MINERA LOS PELAMBRES2996301Tableros electricos 3 tom76354771-KINGENIERIA ENACON SPA331701702019-04-01uv7BUO3yg/7RoMjh1mPXXG/8YIwjtXsu7kcOq7dZQj66QCiY4FVz2fIhF1jaU0GSikq/jq26IFGylGus92OnPQ==Aw==300PI7bw8y0RNUJrGxyhb2gr6BjFtv/Ikyo/6g69wycoXTHSoRML3xvZvOBytreN7REw9JF0Ldoj91RRtaZbH38bA==2019-04-01T01:36:40
DKFS7bNYRpVYLNEII+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-K331702019-04-0196790240-3MINERA LOS PELAMBRES2996301Tableros electricos 3 tom76354771-KINGENIERIA ENACON SPA331701702019-04-01uv7BUO3yg/7RoMjh1mPXXG/8YIwjtXsu7kcOq7dZQj66QCiY4FVz2fIhF1jaU0GSikq/jq26IFGylGus92OnPQ==Aw==300PI7bw8y0RNUJrGxyhb2gr6BjFtv/Ikyo/6g69wycoXTHSoRML3xvZvOBytreN7REw9JF0Ldoj91RRtaZbH38bA==2019-04-01T01:36:40
DKFS7bNYRpVYLNEII+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:02
Sbpc3eOcmYV+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'