├── .gitignore ├── LICENSE ├── README.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── python3-sii.dirs ├── python3-sii.install ├── rules └── source │ ├── format │ └── options ├── docs ├── boleta │ ├── 9_declaracion cumplimiento.pdf │ ├── 9_solicitud set de prueba.pdf │ └── HOWTO ├── ejemplo_F60T33.xml ├── ejemplo_upload_automatico.txt ├── formato_boletas.pdf ├── formato_consumo_folios.pdf ├── formato_dte.pdf ├── formato_intercambio_contribuyentes.pdf ├── formato_libro_boletas.pdf ├── formato_libro_comprasventas.pdf ├── formato_libro_guiadespachos.pdf ├── formato_recibo_mercaderia.pdf ├── instructivo_certificacion.pdf ├── instructivo_retenedores.pdf ├── instructivo_set_pruebas.pdf ├── manual_auto_autenticacion.pdf ├── manual_auto_envio_dte.pdf ├── manual_auto_query_estado_dte.pdf ├── manual_auto_query_estado_envio.pdf ├── manual_certificacion.pdf ├── manual_set_pruebas.pdf ├── schemas │ ├── delme_libro_compra_venta_copy.xsd │ ├── delme_schema_copy.xsd │ ├── docs_boleta_v1.1.xsd │ ├── docs_dte_v1.0.xsd │ ├── docs_sii_types_v1.0.xsd │ ├── libro_boleta_v1.0.xsd │ ├── libro_compra_venta_v1.0.xsd │ ├── libro_guiadespacho_v1.0.xsd │ ├── libroscontables_certificado_autorizacion_v1.0.xsd │ ├── libroscontables_comprobante_certificacion_v1.0.xsd │ ├── libroscontables_sii_types_v1.0.xsd │ ├── orig │ │ ├── ConsumoFolio_v10.xsd │ │ ├── boletas_elec.pdf │ │ ├── consumo_folios.pdf │ │ ├── desc_19983.pdf │ │ ├── diag_boleta.zip │ │ ├── diag_libro_bol.zip │ │ ├── diagrama_19983.zip │ │ ├── diagrama_dte.zip │ │ ├── diagrama_ic.zip │ │ ├── diagrama_iecv.zip │ │ ├── diagrama_lgd.zip │ │ ├── diagrama_resp.zip │ │ ├── diagrama_resp_libros.zip │ │ ├── ejemplo_xml.zip │ │ ├── formato_ic.pdf │ │ ├── libros_boletas.pdf │ │ ├── schema19983.zip │ │ ├── schema_dte.zip │ │ ├── schema_envio_bol.zip │ │ ├── schema_ic.zip │ │ ├── schema_iecv.zip │ │ ├── schema_lgd.zip │ │ ├── schema_libro_bol.zip │ │ ├── schema_resp.zip │ │ └── schema_resp_libros.zip │ ├── ptcl_envio_dte_v1.0.xsd │ ├── ptcl_respuesta_dte_v1.0.xsd │ ├── ptcl_resultado_envio_dte_v1.0.xsd │ └── xml_signature_v1.0.xsd └── ws_formato_envio.pdf ├── files ├── schema │ ├── doc_boleta_venta │ │ └── ConsumoFolio_v10.xsd │ ├── doc_dte │ │ ├── DTE_v10.xsd │ │ ├── EnvioDTE_v10.xsd │ │ ├── SiiTypes_v10.xsd │ │ └── xmldsignature_v10.xsd │ ├── doc_info_elec_compra_venta │ │ ├── LceCal_v10.xsd │ │ ├── LceCoCertif_v10.xsd │ │ ├── LceSiiTypes_v10.xsd │ │ ├── LibroCV_v10.xsd │ │ └── xmldsignature_v10.xsd │ ├── doc_libro_boleta │ │ └── LibroBOLETA_v10.xsd │ ├── doc_libro_guia_despacho │ │ ├── LibroGuia_v10.xsd │ │ └── xmldsignature_v10.xsd │ ├── ptcl_recibo_comercial │ │ ├── EnvioRecibos_v10.xsd │ │ ├── Recibos_v10.xsd │ │ ├── SiiTypes_v10.xsd │ │ └── xmldsignature_v10.xsd │ └── ptcl_respuesta_envio │ │ ├── RespuestaEnvioDTE_v10.xsd │ │ ├── SiiTypes_v10.xsd │ │ └── xmldsignature_v10.xsd └── templ_companies.yml ├── setup.cfg ├── setup.py └── src └── sii ├── __init__.py └── lib ├── __init__.py ├── exchange.py ├── helpers.py ├── lib ├── __init__.py ├── fileio.py ├── format.py ├── output.py ├── shell.py ├── syscall.py └── xml.py ├── printing ├── Document.py ├── SectionBarcode.py ├── SectionDisclaimer.py ├── SectionEmitter.py ├── SectionItems.py ├── SectionPayments.py ├── SectionPreamble.py ├── SectionReceiver.py ├── SectionReferences.py ├── SectionSignature.py ├── SectionSiiPatch.py ├── SectionTotals.py ├── TemplateElement.py ├── __init__.py ├── barcode │ ├── Barcode.py │ ├── PDF417.py │ ├── __init__.py │ └── psbcdelib.ps ├── helpers.py └── printing.py ├── ptcl ├── Seed.py ├── Token.py ├── __init__.py └── helpers.py ├── schemas.py ├── signature.py ├── stamping.py ├── types ├── Branch.py ├── CAF.py ├── CAFPool.py ├── Company.py ├── CompanyPool.py └── __init__.py ├── upload.py └── validation.py /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | build/ 3 | dist/ 4 | *.pyc 5 | *.egg* 6 | *.swp 7 | *.log 8 | *.aux 9 | *.eps 10 | 11 | src/cns/pylib/sii/printing/templates/*.pdf 12 | src/cns/pylib/sii/printing/templates/sandbox/ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chilean Tax Revenue Office (SII) Library. 2 | --- 3 | ## What is it? 4 | It is a Python library aimed at facilitating the interactions with Chile's SII (Tax Revenue Office) requirements for information. This includes (by SII schema definition standards): 5 | - [x] Creation of from 6 | - [x] Creation of LibroVentas. 7 | - [x] Connecting and authentication with SII servers. (automatic session negociation) 8 | - [x] Signing of documents with x509 key/cert. (ask if you want to know how to get them from your .pfx) 9 | - [x] Uploading of sales documents. 10 | - [ ] Uploading of accounting reports. 11 | - [x] Generation of TeX Template. (Unix/Linux) 12 | - [x] Generation of PDF from TeX Template. (Unix/Linux) 13 | - [x] Printing of PDF and TeX Templates (Unix/Linux). 14 | 15 | ## Dependants 16 | * [python-sii-utils](https://github.com/voipir/python-sii-utils.git) (Command Line Utilities) 17 | 18 | ## Requirements 19 | #### Currently this library has been developed and tested on: 20 | * GNU/Linux Debian 8.x Jessie 21 | 22 | For support for a Microsoft OS, it should be possible, but will not be officially supported. We don't work with it, so if anybody wants to take up that task instead, be welcome to do so. We will acommodate your needs on porting as well as we can. 23 | 24 | #### Library Dependencies: 25 | * Python3 (though it should be quite trivial to backport to 2.7 via __future__ includes) 26 | * suds-jerko (currently first choice of SUDS fork that has support for Python3) 27 | * lxml (xml creation and handling) 28 | * xmlsec (signing and verifying of documents) 29 | * jinja2 (for templating of TeX template which then is made a PDF by pdflatex) 30 | * pdflatex (PDF building, could be made optional to ease porting to a Microsoft OS) 31 | 32 | ## How-To 33 | #### Use: 34 | Currently there is no detailed documentation on usage available (TODO). The next best thing to get startet is to take a look at [python-sii-utils](https://github.com/voipir/python-sii-utils.git). There you can see usage cases of the library, covering pretty much all the library functionality. For any help or further info, please create an issue with the tag `help`. 35 | 36 | ## Licence 37 | The library is licenced as LGPLv3 as you can make out in the LICENSE file. You can do what ever you want with this code, as long as you keep any changes or enhancements to this library (not your code that interacts with it!) public. Other than that we would also kindly ask you for fair play and collaboration. We all have the same itch to scratch here, and would greatly benefit from each other, even if by means of a bug report. Thank you!. 38 | 39 | --- 40 | Enjoy! -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-sii (1.0.4) stable; urgency=medium 2 | 3 | * [d5de82a] Fix XML reading from file. 4 | 5 | -- Leonhard Weber Mon, 12 Sep 2016 17:41:45 -0300 6 | 7 | python-sii (1.0.3) stable; urgency=medium 8 | 9 | * [bb7b519] Fixed controlled attribute access on Company and Branch types. 10 | * [e31a62e] Fixed string representation of lib.XML. 11 | * [bf4bb18] Fixed printing items without specified PrcItem. 12 | * [2efcf2b] Thread safety for TeX/PDF generation. 13 | * [5844cfb] Generalized XML dumping and printing in helper lib. 14 | 15 | -- Leonhard Weber Fri, 05 Aug 2016 11:51:35 -0400 16 | 17 | python-sii (1.0.0) stable; urgency=medium 18 | 19 | * Initial Release. 20 | 21 | -- Leonhard Weber Fri, 24 Jun 2016 19:01:32 -0400 22 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-sii 2 | Section: python 3 | Priority: optional 4 | Maintainer: Leonhard Weber 5 | Build-Depends: 6 | dh-python (>= 1.20141111), 7 | debhelper (>= 9), 8 | python3 (>= 3.4.2), 9 | python3-setuptools 10 | Standards-Version: 3.9.8 11 | 12 | Package: python3-sii 13 | Architecture: all 14 | Depends: 15 | ${misc:Depends}, 16 | ${python3:Depends}, 17 | python3-yaml (>= 3.11), 18 | python3-crypto (>= 2.6.1), 19 | python3-xmlsec (>= 0.3.1), 20 | python3-suds (>= 0.7~0), 21 | python3-requests (>= 2.8.1), 22 | texlive-latex-base (>= 2014.20141024), 23 | texlive-latex-extra (>= 2014.20141024), 24 | texlive-latex-recommended (>= 2014.20141024), 25 | texlive-lang-spanish (>= 2014.20141024), 26 | ps2eps (>= 1.68) 27 | Description: SII Common Library. 28 | Implements anything related to interacting with the SII (Chilean Tax Office). 29 | So far, signing and verifying of signatures and schemas of DTE's (electronic 30 | invoices), printing via LaTeX templates and upload/download of information to 31 | SII services/web portal. 32 | . 33 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Files: * 2 | Copyright: Copyright 2016 Empresas Voipir 3 | License: LGPLv3 4 | 5 | Files: debian/* 6 | Copyright: Copyright 2016 Empresas Voipir 7 | License: LGPLv3 8 | -------------------------------------------------------------------------------- /debian/python3-sii.dirs: -------------------------------------------------------------------------------- 1 | /var/lib/sii 2 | /usr/share/doc/python3-sii -------------------------------------------------------------------------------- /debian/python3-sii.install: -------------------------------------------------------------------------------- 1 | files/schema /var/lib/sii/ 2 | files/templ_companies.yml /usr/share/doc/python3-sii/ 3 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export PYBUILD_NAME=sii 4 | export PYBUILD_DISABLE=test # FIXME: do proper instead of just disabling 5 | 6 | 7 | %: 8 | dh $@ --with python3 --buildsystem=pybuild 9 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/source/options: -------------------------------------------------------------------------------- 1 | extend-diff-ignore="\.egg-info" -------------------------------------------------------------------------------- /docs/boleta/9_declaracion cumplimiento.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/boleta/9_declaracion cumplimiento.pdf -------------------------------------------------------------------------------- /docs/boleta/9_solicitud set de prueba.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/boleta/9_solicitud set de prueba.pdf -------------------------------------------------------------------------------- /docs/boleta/HOWTO: -------------------------------------------------------------------------------- 1 | http://www.sii.cl/factura_electronica/guia_emitir_boleta_servicio.htm 2 | -------------------------------------------------------------------------------- /docs/ejemplo_F60T33.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 97975000-5 6 | 7880442-4 7 | 60803000-K 8 | 2003-09-02 9 | 0 10 | 2003-10-13T09:33:22 11 | 12 | 33 13 | 1 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 21 | 60 22 | 2003-10-13 23 | 24 | 25 | 97975000-5 26 | RUT DE PRUEBA 27 | Insumos de Computacion 28 | 31341 29 | 1234 30 | Teatinos 120, Piso 4 31 | Santiago 32 | Santiago 33 | 34 | 35 | 77777777-7 36 | EMPRESA LTDA 37 | COMPUTACION 38 | SAN DIEGO 2222 39 | LA FLORIDA 40 | SANTIAGO 41 | 42 | 43 | 100000 44 | 19 45 | 19000 46 | 119000 47 | 48 | 49 | 50 | 1 51 | 52 | INT1 53 | 011 54 | 55 | Parlantes Multimedia 180W. 56 | 57 | 20 58 | 4500 59 | 90000 60 | 61 | 62 | 2 63 | 64 | INT1 65 | 0231 66 | 67 | Mouse Inalambrico PS/2 68 | 69 | 1 70 | 5000 71 | 5000 72 | 73 | 74 | 3 75 | 76 | INT1 77 | 1515 78 | 79 | Caja de Diskettes 10 Unidades 80 | 81 | 5 82 | 1000 83 | 5000 84 | 85 | 86 |
87 | 97975000-5 88 | 33 89 | 60 90 | 2003-10-13 91 | 77777777-7 92 | EMPRESA LTDA 93 | 119000 94 | Parlantes Multimedia 180W. 95 | 96 | 97 | 97975000-5 98 | RUT DE PRUEBA 99 | 33 100 | 101 | 1 102 | 200 103 | 104 | 2003-09-04 105 | 106 | 0a4O6Kbx8Qj3K4iWSP4w7KneZYeJ+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw== 107 | Aw== 108 | 109 | 100 110 | 111 | g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAavCALKByHzdYAfZ3LhGTXCai5zNxOo4lDQ== 112 | 113 | 2003-10-13T09:33:20 114 |
115 | GbmDcS9e/jVC2LsLIe1iRV12Bf6lxsILtbQiCkh6mbjckFCJ7fj/kakFTS06Jo8i 116 | S4HXvJj3oYZuey53Krniew== 117 |
118 | 2003-10-13T09:33:20 119 |
120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | hlmQtu/AyjUjTDhM3852wvRCr8w= 130 | 131 | 132 | JG1Ig0pvSIH85kIKGRZUjkyX6CNaY08Y94j4UegTgDe8+wl61GzqjdR1rfOK9BGn93AMOo6aiAgolW0k/XklNVtM/ZzpNNS3d/fYVa1q509mAMSXbelxSM3bjoa7H6Wzd/mV1PpQ8zK5gw7mgMMP4IKxHyS92G81GEguSmzcQmA= 133 | 134 | 135 | 136 | 137 | tNEknkb1kHiD1OOAWlLKkcH/UP5UGa6V6MYso++JB+vYMg2OXFROAF7G8BNFFPQx 138 | iuS/7y1azZljN2xq+bW3bAou1bW2ij7fxSXWTJYFZMAyndbLyGHM1e3nVmwpgEpx 139 | BHhZzPvwLb55st1wceuKjs2Ontb13J33sUb7bbJMWh0= 140 | 141 | 142 | AQAB 143 | 144 | 145 | 146 | 147 | MIIEgjCCA+ugAwIBAgIEAQAApzANBgkqhkiG9w0BAQUFADCBtTELMAkGA1UEBhMC 148 | Q0wxHTAbBgNVBAgUFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQHFAhTYW50 149 | aWFnbzEUMBIGA1UEChQLRS1DRVJUQ0hJTEUxIDAeBgNVBAsUF0F1dG9yaWRhZCBD 150 | ZXJ0aWZpY2Fkb3JhMRcwFQYDVQQDFA5FLUNFUlRDSElMRSBDQTEjMCEGCSqGSIb3 151 | DQEJARYUZW1haWxAZS1jZXJ0Y2hpbGUuY2wwHhcNMDMxMDAxMTg1ODE1WhcNMDQw 152 | OTMwMDAwMDAwWjCBuDELMAkGA1UEBhMCQ0wxFjAUBgNVBAgUDU1ldHJvcG9saXRh 153 | bmExETAPBgNVBAcUCFNhbnRpYWdvMScwJQYDVQQKFB5TZXJ2aWNpbyBkZSBJbXB1 154 | ZXN0b3MgSW50ZXJub3MxDzANBgNVBAsUBlBpc28gNDEjMCEGA1UEAxQaV2lsaWJh 155 | bGRvIEdvbnphbGV6IENhYnJlcmExHzAdBgkqhkiG9w0BCQEWEHdnb256YWxlekBz 156 | aWkuY2wwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALxZlVh1xr9sKQIBDF/6 157 | Va+lsHQSG5AAmCWvtNTIOXN3E9EQCy7pOPHrDg6EusvoHyesZSKJbc0TnIFXZp78 158 | q7mbdHijzKqvMmyvwbdP7KK8LQfwf84W4v9O8MJeUHlbJGlo5nFACrPAeTtONbHa 159 | ReyzeMDv2EganNEDJc9c+UNfAgMBAAGjggGYMIIBlDAjBgNVHREEHDAaoBgGCCsG 160 | AQQBwQEBoAwWCjA3ODgwNDQyLTQwCQYDVR0TBAIwADA8BgNVHR8ENTAzMDGgL6At 161 | hitodHRwOi8vY3JsLmUtY2VydGNoaWxlLmNsL2UtY2VydGNoaWxlY2EuY3JsMCMG 162 | A1UdEgQcMBqgGAYIKwYBBAHBAQKgDBYKOTY5MjgxODAtNTAfBgNVHSMEGDAWgBTg 163 | KP3S4GBPs0brGsz1CJEHcjodCDCB0AYDVR0gBIHIMIHFMIHCBggrBgEEAcNSBTCB 164 | tTAvBggrBgEFBQcCARYjaHR0cDovL3d3dy5lLWNlcnRjaGlsZS5jbC8yMDAwL0NQ 165 | Uy8wgYEGCCsGAQUFBwICMHUac0VsIHRpdHVsYXIgaGEgc2lkbyB2YWxpZG8gZW4g 166 | Zm9ybWEgcHJlc2VuY2lhbCwgcXVlZGFuZG8gZWwgQ2VydGlmaWNhZG8gcGFyYSB1 167 | c28gdHJpYnV0YXJpbywgcGFnb3MsIGNvbWVyY2lvIHkgb3Ryb3MwCwYDVR0PBAQD 168 | AgTwMA0GCSqGSIb3DQEBBQUAA4GBABMfCyJF0mNXcov8iEWvjGFyyPTsXwvsYbbk 169 | OJ41wjaGOFMCInb4WY0ngM8BsDV22bGMs8oLyX7rVy16bGA8Z7WDUtYhoOM7mqXw 170 | /Hrpqjh3JgAf8zqdzBdH/q6mAbdvq/yb04JHKWPC7fMFuBoeyVWAnhmuMZfReWQi 171 | MUEHGGIW 172 | 173 | 174 |
175 |
176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 4OTWXyRl5fw3htjTyZXQtYEsC3E= 185 | 186 | 187 | sBnr8Yq14vVAcrN/pKLD/BrqUFczKMW3y1t3JOrdsxhhq6IxvS13SgyMXbIN/T9ciRaFgNabs3pi732XhcpeiSmD1ktzbRctEbSIszYkFJY49k0eB+TVzq3eVaQr4INrymfuOnWj78BZcwKuXvDy4iAcx6/TBbAAkPFwMP9ql2s= 188 | 189 | 190 | 191 | 192 | tNEknkb1kHiD1OOAWlLKkcH/UP5UGa6V6MYso++JB+vYMg2OXFROAF7G8BNFFPQx 193 | iuS/7y1azZljN2xq+bW3bAou1bW2ij7fxSXWTJYFZMAyndbLyGHM1e3nVmwpgEpx 194 | BHhZzPvwLb55st1wceuKjs2Ontb13J33sUb7bbJMWh0= 195 | 196 | 197 | AQAB 198 | 199 | 200 | 201 | 202 | MIIEgjCCA+ugAwIBAgIEAQAApzANBgkqhkiG9w0BAQUFADCBtTELMAkGA1UEBhMC 203 | Q0wxHTAbBgNVBAgUFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQHFAhTYW50 204 | aWFnbzEUMBIGA1UEChQLRS1DRVJUQ0hJTEUxIDAeBgNVBAsUF0F1dG9yaWRhZCBD 205 | ZXJ0aWZpY2Fkb3JhMRcwFQYDVQQDFA5FLUNFUlRDSElMRSBDQTEjMCEGCSqGSIb3 206 | DQEJARYUZW1haWxAZS1jZXJ0Y2hpbGUuY2wwHhcNMDMxMDAxMTg1ODE1WhcNMDQw 207 | OTMwMDAwMDAwWjCBuDELMAkGA1UEBhMCQ0wxFjAUBgNVBAgUDU1ldHJvcG9saXRh 208 | bmExETAPBgNVBAcUCFNhbnRpYWdvMScwJQYDVQQKFB5TZXJ2aWNpbyBkZSBJbXB1 209 | ZXN0b3MgSW50ZXJub3MxDzANBgNVBAsUBlBpc28gNDEjMCEGA1UEAxQaV2lsaWJh 210 | bGRvIEdvbnphbGV6IENhYnJlcmExHzAdBgkqhkiG9w0BCQEWEHdnb256YWxlekBz 211 | aWkuY2wwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALxZlVh1xr9sKQIBDF/6 212 | Va+lsHQSG5AAmCWvtNTIOXN3E9EQCy7pOPHrDg6EusvoHyesZSKJbc0TnIFXZp78 213 | q7mbdHijzKqvMmyvwbdP7KK8LQfwf84W4v9O8MJeUHlbJGlo5nFACrPAeTtONbHa 214 | ReyzeMDv2EganNEDJc9c+UNfAgMBAAGjggGYMIIBlDAjBgNVHREEHDAaoBgGCCsG 215 | AQQBwQEBoAwWCjA3ODgwNDQyLTQwCQYDVR0TBAIwADA8BgNVHR8ENTAzMDGgL6At 216 | hitodHRwOi8vY3JsLmUtY2VydGNoaWxlLmNsL2UtY2VydGNoaWxlY2EuY3JsMCMG 217 | A1UdEgQcMBqgGAYIKwYBBAHBAQKgDBYKOTY5MjgxODAtNTAfBgNVHSMEGDAWgBTg 218 | KP3S4GBPs0brGsz1CJEHcjodCDCB0AYDVR0gBIHIMIHFMIHCBggrBgEEAcNSBTCB 219 | tTAvBggrBgEFBQcCARYjaHR0cDovL3d3dy5lLWNlcnRjaGlsZS5jbC8yMDAwL0NQ 220 | Uy8wgYEGCCsGAQUFBwICMHUac0VsIHRpdHVsYXIgaGEgc2lkbyB2YWxpZG8gZW4g 221 | Zm9ybWEgcHJlc2VuY2lhbCwgcXVlZGFuZG8gZWwgQ2VydGlmaWNhZG8gcGFyYSB1 222 | c28gdHJpYnV0YXJpbywgcGFnb3MsIGNvbWVyY2lvIHkgb3Ryb3MwCwYDVR0PBAQD 223 | AgTwMA0GCSqGSIb3DQEBBQUAA4GBABMfCyJF0mNXcov8iEWvjGFyyPTsXwvsYbbk 224 | OJ41wjaGOFMCInb4WY0ngM8BsDV22bGMs8oLyX7rVy16bGA8Z7WDUtYhoOM7mqXw 225 | /Hrpqjh3JgAf8zqdzBdH/q6mAbdvq/yb04JHKWPC7fMFuBoeyVWAnhmuMZfReWQi 226 | MUEHGGIW 227 | 228 | 229 |
230 | -------------------------------------------------------------------------------- /docs/ejemplo_upload_automatico.txt: -------------------------------------------------------------------------------- 1 | POST /cgi_dte/UPL/DTEUpload HTTP/1.0 2 | Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/ms-excel, application/msword, */* 3 | Referer: http://www.empresa.cl 4 | Accept-Language: es-cl 5 | Content-Type: multipart/form-data: boundary=---------------9022632e1130lc4 6 | Accept-Encoding: gzip, deflate 7 | User-Agent: Mozilla/4.0 (compatible; PROG 1.0; Windows NT 5.0; YComp 5.0.2.4) 8 | Content-Length: 8653 9 | Connection: Keep-Alive 10 | Cache-Control: no-cache 11 | Cookie: TOKEN=YJyar2VB0HWzg 12 | 13 | -----------------9022632e1130lc4 14 | Content-Disposition: form-data; name="rutSender" 15 | Content-Type: text/plain; charset=US-ASCII 16 | Content-Transfer-Encoding: 8Bit 17 | 18 | 66000000 19 | -----------------9022632e1130lc4 20 | Content-Disposition: form-data; name="dvSender" 21 | Content-Type: text/plain; charset=US-ASCII 22 | Content-Transfer-Encoding: 8Bit 23 | 24 | 0 25 | -----------------9022632e1130lc4 26 | Content-Disposition: form-data; name="rutCompany" 27 | Content-Type: text/plain; charset=US-ASCII 28 | Content-Transfer-Encoding: 8Bit 29 | 30 | 77777777 31 | -----------------9022632e1130lc4 32 | Content-Disposition: form-data; name="dvCompany" 33 | Content-Type: text/plain; charset=US-ASCII 34 | Content-Transfer-Encoding: 8Bit 35 | 36 | 7 37 | -----------------9022632e1130lc4 38 | Content-Disposition: form-data; name="archivo"; filename="d:\ENVFIN_100_sign.xml" 39 | Content-Type: application/octet-stream 40 | Content-Transfer-Encoding: binary 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 77777777-7 51 | 52 | 66000000-0 53 | 54 | 60803000-K 55 | 56 | 2002-10-20 57 | 58 | 0 59 | 60 | 2003-08-29T19:34:59 61 | 62 | 63 | 64 | 33 65 | 66 | 1 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 33 81 | 82 | 996286 83 | 84 | 2003-08-29 85 | 86 | 1 87 | 88 | 89 | 90 | 91 | 92 | 77777777-7 93 | 94 | SIN RAZON SOCIAL/NOMBRES 95 | 96 | Prueba SII 97 | 98 | 31341 99 | 100 | 1234 101 | 102 | Teatinos 120, Piso 4 103 | 104 | Santiago 105 | 106 | Santiago 107 | 108 | 109 | 110 | 111 | 112 | 55555555-5 113 | 114 | J.L. LTDA 115 | 116 | COMPUTACION 117 | 118 | SAN DIEGO 55228 119 | 120 | LA FLORIDA 121 | 122 | SANTIAGO 123 | 124 | 125 | 126 | 127 | 128 | 9000 129 | 130 | 1620 131 | 132 | 10620 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 1 141 | 142 | 143 | 144 | INT1 145 | 146 | 011 147 | 148 | 149 | 150 | Computadores Personales 151 | 152 | 153 | 154 | 3 155 | 156 | 3000 157 | 158 | 9000 159 | 160 | 161 | 162 | 163 | 164 |
165 | 166 | 77777777-7 167 | 168 | 33 169 | 170 | 996286 171 | 172 | 2003-08-29 173 | 174 | 55555555-5 175 | 176 | J.L. LTDA 177 | 178 | 10620 179 | 180 | Computadores Personales 181 | 182 | 183 | 184 | 185 | 186 | 77777777-7 187 | 188 | SIN RAZON SOCIAL/NOMBRES 189 | 190 | 33 191 | 192 | 193 | 194 | 996281 195 | 196 | 996290 197 | 198 | 199 | 200 | 2003-08-22 201 | 202 | 203 | 204 | 0u0BSJDPr2LCXj6keU/QziakYvgwVrN6qQe7lozfpqrYok33HJpSXFbUTSGivT0Yj+jDKxXvFe3hZt8UQzgV/Q== 205 | 206 | Aw== 207 | 208 | 209 | 210 | 100 211 | 212 | 213 | 214 | gRgTix54vIl4d3Zl1I0L+ESmXbViAw+SbANU0ACOKSIvW68cigBW/OjUekep2FPrPwKy1DaKJIQaDD7xaxWMNg== 215 | 216 | 217 | 218 | 2003-08-29T19:17:10 219 | 220 |
221 | 222 | UWT6qg4c/T2thvO8LjLQ9NMG52ym0ZnJjOp7G3zRsXSk46+OH2hoNR/GUqtkWfwIPgOIon7gnzDdv0Wame2BbA== 223 | 224 |
225 | 226 | 2003-08-29T19:17:10 227 | 228 |
229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | U4UKWVXeeDfZf6Z2GLpdyIr9qS4= 249 | 250 | 251 | 252 | 253 | 254 | UT4Hvspx+kKQWCSGPZePM5F2mikp3LW9LYP20TWl3Bcu+51AOhWeUwj61UfrY+L+GhCkDC0cI2ushVRJIINGr4yttf7riIpuECbAyiC3G2mZP6GHk+Vf+roLDYvbhR1tGyctFFzzBobW+n4GQcLZ+vIu6KC24Kvj5KK2Y1HKh5s= 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | tNEknkb1kHiD1OOAWlLKkcH/UP5UGa6V6MYso++JB+vYMg2OXFROAF7G8BNFFPQx 265 | 266 | iuS/7y1azZljN2xq+bW3bAou1bW2ij7fxSXWTJYFZMAyndbLyGHM1e3nVmwpgEpx 267 | 268 | BHhZzPvwLb55st1wceuKjs2Ontb13J33sUb7bbJMWh0= 269 | 270 | 271 | 272 | 273 | 274 | AQAB 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | WJCATjpLA6mgAwIBAgIDAgGKMAsGCSqGSIb3DQEBBDCBsTEdMBsGA1UECBQUUmVn 285 | 286 | aW9uIE1ldHJvcG9saXRhbmExETAPBgNVBAcUCFNhbnRpYWdvMSIwIAYDVQQDFBlF 287 | 288 | LUNlcnRjaGlsZSBDQSBJbnRlcm1lZGlhMTYwNAYDVQQLFC1FbXByZXNhIE5hY2lv 289 | 290 | bmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExFDASBgNVBAoUC0UtQ0VS 291 | 292 | VENISUxFMQswCQYDVQQGEwJDTDAeFw0wMjEwMDIxOTExNTlaFw0wMzEwMDIwMDAw 293 | 294 | MDBaMIHXMR0wGwYDVQQIFBRSZWdpb24gTWV0cm9wb2xpdGFuYTEnMCUGA1UECxQe 295 | 296 | U2VydmljaW8gZGUgSW1wdWVzdG9zIEludGVybm9zMScwJQYDVQQKFB5TZXJ2aWNp 297 | 298 | byBkZSBJbXB1ZXN0b3MgSW50ZXJub3MxETAPBgNVBAcUCFNhbnRpYWdvMR8wHQYJ 299 | 300 | KoZIhvcNAQkBFhB3Z29uemFsZXpAc2lpLmNsMSMwIQYDVQQDFBpXaWxpYmFsZG8g 301 | 302 | R29uemFsZXogQ2FicmVyYTELMAkGA1UEBhMCQ0wwXDANBgkqhkiG9w0BAQEFAANL 303 | 304 | ADBIAkEAvNQyaLPd3cQlBr0fQJCSAKXSFan/WbaFtD5P7QDzcE1pBIvKY2Uv6uid 305 | 306 | ur/mGVB9IS4Fq/1xRIXy13FFmxLwTQIDAQABo4IBgjCCAX4wIwYDVR0RBBwwGqAY 307 | 308 | BggrBgEEAcNSAaAMFgowNzg4MDQ0Mi00MDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6 309 | 310 | Ly9jcmwuZS1jZXJ0Y2hpbGUuY2wvRWNlcnRjaGlsZUNBSS5jcmwwIwYDVR0SBBww 311 | 312 | GqAYBggrBgEEAcEBAqAMFgo5NjkyjCs4MC01MIHmBgNVHSAEgd4wgdswgdgGCCsG 313 | 314 | AQQBw1IAMIHLMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3LmUtY2VydGNoaWxlLmNs 315 | 316 | L3BvbGl0aWNhL2Nwcy5odG0wgZAGCCsGAQUFBwICMIGDGoGARWwgdGl0dWxhciBo 317 | 318 | YSBzaWRvIHZhbGlkYWRvIGVuIGZvcm1hIHByZXNlbmNpYWwsIHF1ZWRhbmRvIGhh 319 | 320 | YmlsaXRhZG8gZWwgQ2VydGlmaWNhZG8gcGFyYSB1c28gdHJpYnV0YXJpbywgcGFn 321 | 322 | b3MsIGNvbWVyY2lvIHUgb3Ryb3MwCwYDVR0PBAQDAgTwMAsGCSqGSIb3DQEBBAOB 323 | 324 | gQB2V4cTj7jo1RawmsRQUSnnvJjMCrZstcHY+Ss3IghVPO9eGoYzu5Q63vzt0Pi8 325 | 326 | CS91SBc7xo+LDoljaUyjOzj7zvU7TpWoFndiTQF3aCOtTkV+vjCMWW3sVHes4UCM 327 | 328 | DkF3VYK+rDTAadiaeDArTwsx4eNEpxFuA/TJwcXpLQRCDg== 329 | 330 | 331 | 332 | 333 | 334 |
335 | 336 |
337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | B58sD6ZjbP/D+dcucrU1oXzqyAM= 355 | 356 | 357 | 358 | 359 | 360 | mDSRXEXIPob8CL9UG5PI09Vm4B84nKqSnG+xrtgFPqqE3Y3t5bLVyuNBJgOjgLV5RAngogXoGeJD/CPn/sFxym7ZCJnFtRHlip6Wta7nQNPeNLvaAbBJYzvusqn7rJemUiOop/qGec9Bfw353fNuc/V2/GyV89JqVWgSK4273M0= 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | tNEknkb1kHiD1OOAWlLKkcH/UP5UGa6V6MYso++JB+vYMg2OXFROAF7G8BNFFPQx 371 | 372 | iuS/7y1azZljN2xq+bW3bAou1bW2ij7fxSXWTJYFZMAyndbLyGHM1e3nVmwpgEpx 373 | 374 | BHhZzPvwLb55st1wceuKjs2Ontb13J33sUb7bbJMWh0= 375 | 376 | 377 | 378 | 379 | 380 | AQAB 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | WJCATjpLA6mgAwIBAgIDAgGKMAsGCSqGSIb3DQEBBDCBsTEdMBsGA1UECBQUUmVn 391 | aW9uIE1ldHJvcG9saXRhbmExETAPBgNVBAcUCFNhbnRpYWdvMSIwIAYDVQQDFBlF 392 | LUNlcnRjaGlsZSBDQSBJbnRlcm1lZGlhMTYwNAYDVQQLFC1FbXByZXNhIE5hY2lv 393 | bmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExFDASBgNVBAoUC0UtQ0VS 394 | VENISUxFMQswCQYDVQQGEwJDTDAeFw0wMjEwMDIxOTExNTlaFw0wMzEwMDIwMDAw 395 | MDBaMIHXMR0wGwYDVQQIFBRSZWdpb24gTWV0cm9wb2xpdGFuYTEnMCUGA1UECxQe 396 | U2VydmljaW8gZGUgSW1wdWVzdG9zIEludGVybm9zMScwJQYDVQQKFB5TZXJ2aWNp 397 | byBkZSBJbXB1ZXN0b3MgSW50ZXJub3MxETAPBgNVBAcUCFNhbnRpYWdvMR8wHQYJ 398 | KoZIhvcNAQkBFhB3Z29uemFsZXpAc2lpLmNsMSMwIQYDVQQDFBpXaWxpYmFsZG8g 399 | R29uemFsZXogQ2FicmVyYTELMAkGA1UEBhMCQ0wwXDANBgkqhkiG9w0BAQEFAANL 400 | ADBIAkEAvNQyaLPd3cQlBr0fQJCSAKXSFan/WbaFtD5P7QDzcE1pBIvKY2Uv6uid 401 | ur/mGVB9IS4Fq/1xRIXy13FFmxLwTQIDAQABo4IBgjCCAX4wIwYDVR0RBBwwGqAY 402 | BggrBgEEAcNSAaAMFgowNzg4MDQ0Mi00MDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6 403 | Ly9jcmwuZS1jZXJ0Y2hpbGUuY2wvRWNlcnRjaGlsZUNBSS5jcmwwIwYDVR0SBBww 404 | GqAYBggrBgEEAcEBAqAMFgo5NjkyjCs4MC01MIHmBgNVHSAEgd4wgdswgdgGCCsG 405 | AQQBw1IAMIHLMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3LmUtY2VydGNoaWxlLmNs 406 | L3BvbGl0aWNhL2Nwcy5odG0wgZAGCCsGAQUFBwICMIGDGoGARWwgdGl0dWxhciBo 407 | YSBzaWRvIHZhbGlkYWRvIGVuIGZvcm1hIHByZXNlbmNpYWwsIHF1ZWRhbmRvIGhh 408 | YmlsaXRhZG8gZWwgQ2VydGlmaWNhZG8gcGFyYSB1c28gdHJpYnV0YXJpbywgcGFn 409 | b3MsIGNvbWVyY2lvIHUgb3Ryb3MwCwYDVR0PBAQDAgTwMAsGCSqGSIb3DQEBBAOB 410 | gQB2V4cTj7jo1RawmsRQUSnnvJjMCrZstcHY+Ss3IghVPO9eGoYzu5Q63vzt0Pi8 411 | CS91SBc7xo+LDoljaUyjOzj7zvU7TpWoFndiTQF3aCOtTkV+vjCMWW3sVHes4UCM 412 | DkF3VYK+rDTAadiaeDArTwsx4eNEpxFuA/TJwcXpLQRCDg== 413 | 414 | 415 | 416 | 417 | 418 |
419 | 420 | 421 | -----------------9022632e1130lc4-- 422 | -------------------------------------------------------------------------------- /docs/formato_boletas.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_boletas.pdf -------------------------------------------------------------------------------- /docs/formato_consumo_folios.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_consumo_folios.pdf -------------------------------------------------------------------------------- /docs/formato_dte.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_dte.pdf -------------------------------------------------------------------------------- /docs/formato_intercambio_contribuyentes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_intercambio_contribuyentes.pdf -------------------------------------------------------------------------------- /docs/formato_libro_boletas.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_libro_boletas.pdf -------------------------------------------------------------------------------- /docs/formato_libro_comprasventas.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_libro_comprasventas.pdf -------------------------------------------------------------------------------- /docs/formato_libro_guiadespachos.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_libro_guiadespachos.pdf -------------------------------------------------------------------------------- /docs/formato_recibo_mercaderia.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/formato_recibo_mercaderia.pdf -------------------------------------------------------------------------------- /docs/instructivo_certificacion.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/instructivo_certificacion.pdf -------------------------------------------------------------------------------- /docs/instructivo_retenedores.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/instructivo_retenedores.pdf -------------------------------------------------------------------------------- /docs/instructivo_set_pruebas.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/instructivo_set_pruebas.pdf -------------------------------------------------------------------------------- /docs/manual_auto_autenticacion.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/manual_auto_autenticacion.pdf -------------------------------------------------------------------------------- /docs/manual_auto_envio_dte.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/manual_auto_envio_dte.pdf -------------------------------------------------------------------------------- /docs/manual_auto_query_estado_dte.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/manual_auto_query_estado_dte.pdf -------------------------------------------------------------------------------- /docs/manual_auto_query_estado_envio.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/manual_auto_query_estado_envio.pdf -------------------------------------------------------------------------------- /docs/manual_certificacion.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/manual_certificacion.pdf -------------------------------------------------------------------------------- /docs/manual_set_pruebas.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/manual_set_pruebas.pdf -------------------------------------------------------------------------------- /docs/schemas/delme_libro_compra_venta_copy.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/delme_libro_compra_venta_copy.xsd -------------------------------------------------------------------------------- /docs/schemas/docs_boleta_v1.1.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/docs_boleta_v1.1.xsd -------------------------------------------------------------------------------- /docs/schemas/docs_dte_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/docs_dte_v1.0.xsd -------------------------------------------------------------------------------- /docs/schemas/docs_sii_types_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/docs_sii_types_v1.0.xsd -------------------------------------------------------------------------------- /docs/schemas/libro_boleta_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/libro_boleta_v1.0.xsd -------------------------------------------------------------------------------- /docs/schemas/libro_compra_venta_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/libro_compra_venta_v1.0.xsd -------------------------------------------------------------------------------- /docs/schemas/libro_guiadespacho_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/libro_guiadespacho_v1.0.xsd -------------------------------------------------------------------------------- /docs/schemas/libroscontables_certificado_autorizacion_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/libroscontables_certificado_autorizacion_v1.0.xsd -------------------------------------------------------------------------------- /docs/schemas/libroscontables_comprobante_certificacion_v1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Comprobante de Certificacion 13 | 14 | 15 | 16 | 17 | 18 | Documento de Comprobante de Certificacion 19 | 20 | 21 | 22 | 23 | 24 | RUT Contribuyente de los LCE 25 | 26 | 27 | 28 | 29 | Fecha de Emision del Comprobante de Certificacion (AAAA-MM-DD) 30 | 31 | 32 | 33 | 34 | 35 | RUT autorizado por el Distribuidor a firmar este documento. 36 | 37 | 38 | 39 | 40 | Fecha y Hora de la Firma 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | Firma Digital sobre Documento 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /docs/schemas/libroscontables_sii_types_v1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | Rol Unico Tributario (99..99-X) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Folio de DTE - 10 digitos 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Monto de 18 digitos y 4 decimales 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Impuestos Adicionales 37 | 38 | 39 | 40 | 41 | IVA Margen de Comercializacion 42 | 43 | 44 | 45 | 46 | IVA Retenido Total 47 | 48 | 49 | 50 | 51 | IVA Retenido Parcial 52 | 53 | 54 | 55 | 56 | IVA Anticipado Faenamiento Carne 57 | 58 | 59 | 60 | 61 | IVA Anticipado Carne 62 | 63 | 64 | 65 | 66 | IVA Anticipado Harina 67 | 68 | 69 | 70 | 71 | Impuesto Art. 37 Letras a, b, c 72 | 73 | 74 | 75 | 76 | Impuesto Art. 42 Ley 825/74 Letra a 77 | 78 | 79 | 80 | 81 | Impuesto Art. 42 Letra c 82 | 83 | 84 | 85 | 86 | Impuesto Art. 42 Letra c 87 | 88 | 89 | 90 | 91 | Impuesto Art. 42 Letra d y e 92 | 93 | 94 | 95 | 96 | Impuesto Especifico Diesel 97 | 98 | 99 | 100 | 101 | Recuperacion Impuesto Especifico Diesel Transportistas 102 | 103 | 104 | 105 | 106 | IVA Retenido Legumbres 107 | 108 | 109 | 110 | 111 | IVA Retenido Silvestres 112 | 113 | 114 | 115 | 116 | IVA Retenido Ganado 117 | 118 | 119 | 120 | 121 | IVA Retenido Madera 122 | 123 | 124 | 125 | 126 | IVA Retenido Trigo 127 | 128 | 129 | 130 | 131 | Impuesto Especifico Gasolina 132 | 133 | 134 | 135 | 136 | IVA Retenido Arroz 137 | 138 | 139 | 140 | 141 | IVA Retenido Hidrobiologicas 142 | 143 | 144 | 145 | 146 | IVA Retenido Chatarra 147 | 148 | 149 | 150 | 151 | IVA Retenido PPA 152 | 153 | 154 | 155 | 156 | IVA Retenido Opcional 157 | 158 | 159 | 160 | 161 | Impuesto Art. 37 Letras e, f, g y h 162 | 163 | 164 | 165 | 166 | Impuesto Art. 37 Letra j 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | Monto 18 digitos (> cero) 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | Porcentaje (3 enteros y 2 decimales) 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | Tipos de Documentos 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | Monto 18 digitos (positivo o negativo) 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | lapso de tiempo. En forma AAAA-MM hasta AAAA-MM 258 | 259 | 260 | 261 | 262 | Inicio del Periodo. En formato AAAA-MM 263 | 264 | 265 | 266 | 267 | Final del Periodo. Formato AAAA-MM 268 | 269 | 270 | 271 | 272 | 273 | 274 | Monto 18 digitos (mayor o igual a cero) 275 | 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /docs/schemas/orig/ConsumoFolio_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/ConsumoFolio_v10.xsd -------------------------------------------------------------------------------- /docs/schemas/orig/boletas_elec.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/boletas_elec.pdf -------------------------------------------------------------------------------- /docs/schemas/orig/consumo_folios.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/consumo_folios.pdf -------------------------------------------------------------------------------- /docs/schemas/orig/desc_19983.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/desc_19983.pdf -------------------------------------------------------------------------------- /docs/schemas/orig/diag_boleta.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diag_boleta.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diag_libro_bol.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diag_libro_bol.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diagrama_19983.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diagrama_19983.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diagrama_dte.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diagrama_dte.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diagrama_ic.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diagrama_ic.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diagrama_iecv.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diagrama_iecv.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diagrama_lgd.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diagrama_lgd.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diagrama_resp.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diagrama_resp.zip -------------------------------------------------------------------------------- /docs/schemas/orig/diagrama_resp_libros.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/diagrama_resp_libros.zip -------------------------------------------------------------------------------- /docs/schemas/orig/ejemplo_xml.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/ejemplo_xml.zip -------------------------------------------------------------------------------- /docs/schemas/orig/formato_ic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/formato_ic.pdf -------------------------------------------------------------------------------- /docs/schemas/orig/libros_boletas.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/libros_boletas.pdf -------------------------------------------------------------------------------- /docs/schemas/orig/schema19983.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema19983.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_dte.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_dte.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_envio_bol.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_envio_bol.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_ic.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_ic.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_iecv.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_iecv.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_lgd.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_lgd.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_libro_bol.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_libro_bol.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_resp.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_resp.zip -------------------------------------------------------------------------------- /docs/schemas/orig/schema_resp_libros.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/orig/schema_resp_libros.zip -------------------------------------------------------------------------------- /docs/schemas/ptcl_envio_dte_v1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | Envio de Documentos Tributarios Electronicos 18 | 19 | 20 | 21 | 22 | 23 | Conjunto de DTE enviados 24 | 25 | 26 | 27 | 28 | 29 | Resumen de Informacion Enviada 30 | 31 | 32 | 33 | 34 | 35 | RUT Emisor de los DTE 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | RUT que envia los DTE 44 | 45 | 46 | 47 | 48 | RUT al que se le envian los DTE 49 | 50 | 51 | 52 | 53 | Fecha de Resolucion que Autoriza el Envio de DTE (AAAA-MM-DD) 54 | Fecha de Resolucion que Autoriza el Envio de DTE (AAAA-MM-DD) 55 | 56 | 57 | 58 | 59 | Numero de Resolucion que Autoriza el Envio de DTE 60 | 61 | 62 | 63 | 64 | Fecha y Hora de la Firma del Archivo de Envio 65 | 66 | 67 | 68 | 69 | Subtotales de DTE enviados 70 | 71 | 72 | 73 | 74 | 75 | Tipo de DTE Enviado 76 | 77 | 78 | 79 | 80 | Numero de DTE Enviados 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | Documento Tributario Electronico 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | Firma Digital sobre SetDTE 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/schemas/ptcl_resultado_envio_dte_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/ptcl_resultado_envio_dte_v1.0.xsd -------------------------------------------------------------------------------- /docs/schemas/xml_signature_v1.0.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/schemas/xml_signature_v1.0.xsd -------------------------------------------------------------------------------- /docs/ws_formato_envio.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/docs/ws_formato_envio.pdf -------------------------------------------------------------------------------- /files/schema/doc_boleta_venta/ConsumoFolio_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_boleta_venta/ConsumoFolio_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_dte/DTE_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_dte/DTE_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_dte/EnvioDTE_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_dte/EnvioDTE_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_dte/SiiTypes_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_dte/SiiTypes_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_dte/xmldsignature_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_dte/xmldsignature_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_info_elec_compra_venta/LceCal_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_info_elec_compra_venta/LceCal_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_info_elec_compra_venta/LceCoCertif_v10.xsd: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Comprobante de Certificacion 13 | 14 | 15 | 16 | 17 | 18 | Documento de Comprobante de Certificacion 19 | 20 | 21 | 22 | 23 | 24 | RUT Contribuyente de los LCE 25 | 26 | 27 | 28 | 29 | Fecha de Emision del Comprobante de Certificacion (AAAA-MM-DD) 30 | 31 | 32 | 33 | 34 | 35 | RUT autorizado por el Distribuidor a firmar este documento. 36 | 37 | 38 | 39 | 40 | Fecha y Hora de la Firma 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | Firma Digital sobre Documento 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /files/schema/doc_info_elec_compra_venta/LceSiiTypes_v10.xsd: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | Rol Unico Tributario (99..99-X) 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Folio de DTE - 10 digitos 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Monto de 30 digitos y 4 decimales 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Impuestos Adicionales 40 | 41 | 42 | 43 | 44 | IVA Margen de Comercializacion 45 | 46 | 47 | 48 | 49 | IVA Retenido Total 50 | 51 | 52 | 53 | 54 | IVA Retenido Parcial 55 | 56 | 57 | 58 | 59 | IVA Anticipado Faenamiento Carne 60 | 61 | 62 | 63 | 64 | IVA Anticipado Carne 65 | 66 | 67 | 68 | 69 | IVA Anticipado Harina 70 | 71 | 72 | 73 | 74 | Impuesto Art. 37 Letras a, b, c 75 | 76 | 77 | 78 | 79 | Impuesto Art. 42 Ley 825/74 Letra a 80 | 81 | 82 | 83 | 84 | Impuesto Art. 42 Letra c 85 | 86 | 87 | 88 | 89 | Impuesto Art. 42 Letra c 90 | 91 | 92 | 93 | 94 | Impuesto Art. 42 Letra d y e 95 | 96 | 97 | 98 | 99 | Impuesto Especifico Diesel 100 | 101 | 102 | 103 | 104 | Recuperacion Impuesto Especifico Diesel Transportistas 105 | 106 | 107 | 108 | 109 | IVA Retenido Legumbres 110 | 111 | 112 | 113 | 114 | IVA Retenido Silvestres 115 | 116 | 117 | 118 | 119 | IVA Retenido Ganado 120 | 121 | 122 | 123 | 124 | IVA Retenido Madera 125 | 126 | 127 | 128 | 129 | IVA Retenido Trigo 130 | 131 | 132 | 133 | 134 | Impuesto Especifico Gasolina 135 | 136 | 137 | 138 | 139 | IVA Retenido Arroz 140 | 141 | 142 | 143 | 144 | IVA Retenido Hidrobiologicas 145 | 146 | 147 | 148 | 149 | IVA Retenido Chatarra 150 | 151 | 152 | 153 | 154 | IVA Retenido PPA 155 | 156 | 157 | 158 | 159 | IVA Retenido Opcional 160 | 161 | 162 | 163 | 164 | Impuesto Art. 37 Letras e, f, g y h 165 | 166 | 167 | 168 | 169 | Impuesto Art. 37 Letra j 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | Monto 30 digitos (> cero) 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | Porcentaje (3 enteros y 2 decimales) 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | Tipos de Documentos 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | Monto 30 digitos (positivo o negativo) 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | lapso de tiempo. En forma AAAA-MM hasta AAAA-MM 261 | 262 | 263 | 264 | 265 | Inicio del Periodo. En formato AAAA-MM 266 | 267 | 268 | 269 | 270 | Final del Periodo. Formato AAAA-MM 271 | 272 | 273 | 274 | 275 | 276 | 277 | Monto 30 digitos (mayor o igual a cero) 278 | 279 | 280 | 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /files/schema/doc_info_elec_compra_venta/LibroCV_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_info_elec_compra_venta/LibroCV_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_info_elec_compra_venta/xmldsignature_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_info_elec_compra_venta/xmldsignature_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_libro_boleta/LibroBOLETA_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_libro_boleta/LibroBOLETA_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_libro_guia_despacho/LibroGuia_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_libro_guia_despacho/LibroGuia_v10.xsd -------------------------------------------------------------------------------- /files/schema/doc_libro_guia_despacho/xmldsignature_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/doc_libro_guia_despacho/xmldsignature_v10.xsd -------------------------------------------------------------------------------- /files/schema/ptcl_recibo_comercial/EnvioRecibos_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/ptcl_recibo_comercial/EnvioRecibos_v10.xsd -------------------------------------------------------------------------------- /files/schema/ptcl_recibo_comercial/Recibos_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/ptcl_recibo_comercial/Recibos_v10.xsd -------------------------------------------------------------------------------- /files/schema/ptcl_recibo_comercial/SiiTypes_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/ptcl_recibo_comercial/SiiTypes_v10.xsd -------------------------------------------------------------------------------- /files/schema/ptcl_recibo_comercial/xmldsignature_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/ptcl_recibo_comercial/xmldsignature_v10.xsd -------------------------------------------------------------------------------- /files/schema/ptcl_respuesta_envio/RespuestaEnvioDTE_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/ptcl_respuesta_envio/RespuestaEnvioDTE_v10.xsd -------------------------------------------------------------------------------- /files/schema/ptcl_respuesta_envio/SiiTypes_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/ptcl_respuesta_envio/SiiTypes_v10.xsd -------------------------------------------------------------------------------- /files/schema/ptcl_respuesta_envio/xmldsignature_v10.xsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voipir/python-sii/9331be62f3ff38af66cde7c3cb334ce87170b75d/files/schema/ptcl_respuesta_envio/xmldsignature_v10.xsd -------------------------------------------------------------------------------- /files/templ_companies.yml: -------------------------------------------------------------------------------- 1 | 123456978: 2 | rut_full : '12345678-9' 3 | rut_base : 12345678 4 | rut_chkdgt : 9 5 | name_long : 'Compañía de Papeles y Lápizes Limitada' 6 | name_short : 'Cia. Papeles y Lápizes Ltda.' 7 | name_activity : 'Papeles e Insumos de Oficina' 8 | addr_street : 'Terra N°42' 9 | addr_commune : 'Espiral Gamma' 10 | addr_city : 'Sol' 11 | addr_phone : '+99 1111 1111' 12 | sii_emitter : '87654321-0' 13 | sii_acteco : [123456, 456789] 14 | sii_office : 0 15 | sii_office_location : 'Sol' 16 | sii_cert : 0 17 | sii_cert_date : '2199-10-10' 18 | resource_logo_eps : '/path/to/my/static/stuff/12345678.logo.eps' 19 | resource_logo_svg : '/path/to/my/static/stuff/12345678.logo.svg' 20 | branches: 21 | - 22 | sii_code : 56987 23 | name : 'Casa Matriz' 24 | address : 'Terra N°42' 25 | commune : 'Espiral Gamma' 26 | city : 'Sol' 27 | phone : '+99 1111 1111' 28 | - 29 | sii_code : 54321 30 | name : 'Sucursal Marte' 31 | address : 'Marte N°24' 32 | commune : 'Espiral Gamma' 33 | city : 'Sol' 34 | phone : '+99 1114 2111' 35 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts=-v 3 | python_files=test/*.py 4 | python_classes=Test 5 | python_functions=test check should enforces excepts it 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ SII Common Library. 2 | """ 3 | from setuptools import setup, find_packages 4 | 5 | 6 | cfg = { 7 | 'name' : 'python-sii', 8 | 'long_description' : __doc__, 9 | 'version' : '1.0.4', 10 | 'packages' : find_packages('src'), 11 | 'package_dir' : {'': 'src'}, 12 | 13 | 'namespace_packages': ['sii'], 14 | 15 | 'install_requires' : [ 16 | 'lxml >= 3.4.0', 17 | 'PyYAML >= 3.11', 18 | 'pycrypto >= 2.6.1', 19 | 'xmlsec >= 0.3.1', 20 | 'suds-jurko >= 0.7.dev0', 21 | 'requests >= 2.8.1' 22 | 23 | # Additionally requires LaTex if you pretend to use printing. 24 | # See debian/control for that. Possibly unavailable under Windows. 25 | ], 26 | 27 | 'include_package_data' : True, 28 | 'package_data' : { 29 | 'sii.lib.printing.barcode': ['*.ps'] 30 | }, 31 | 32 | 'dependency_links' : [], 33 | 'zip_safe' : True 34 | } 35 | 36 | 37 | if __name__ == '__main__': 38 | setup(**cfg) 39 | -------------------------------------------------------------------------------- /src/sii/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | __import__('pkg_resources').declare_namespace(__name__) 3 | except ImportError: 4 | from pkgutil import extend_path 5 | __path__ = extend_path(__path__, __name__) 6 | -------------------------------------------------------------------------------- /src/sii/lib/__init__.py: -------------------------------------------------------------------------------- 1 | """ CNS Common Library for SII stuff. 2 | """ 3 | 4 | from . import printing 5 | from . import ptcl 6 | from . import types 7 | 8 | from . import exchange 9 | from . import schemas 10 | from . import signature 11 | from . import upload 12 | from . import validation 13 | 14 | __all__ = [ 15 | 'exchange', 16 | 'signature', 17 | 'validation', 18 | 'printing', 19 | 'schemas', 20 | 'upload' 21 | ] 22 | -------------------------------------------------------------------------------- /src/sii/lib/helpers.py: -------------------------------------------------------------------------------- 1 | """ Common Local Utilities. 2 | """ 3 | import re 4 | import io 5 | 6 | from lxml import etree 7 | 8 | __all__ = [ 9 | 'prepend_dtd', 10 | 'extract_signode', 11 | 'extract_signodes', 12 | 'extract_signode_reference', 13 | 'extract_signode_certificate' 14 | ] 15 | 16 | DTD_PREAMBLE = """ 17 | 19 | 20 | 21 | 22 | 23 | 24 | ]> 25 | """ 26 | 27 | 28 | def prepend_dtd(xml): 29 | """ Prepends a DTD providing a definition of a to a non-standard xml:id pointer. Necessary for 30 | signature and signature verification. 31 | 32 | :param `etree.Element` xml: XML tree to prepend the DTD to. 33 | 34 | :param str sig_tag: Tag name to contain the URI. 35 | :param str uri_attr: Attribute name to contain the URI. 36 | 37 | :return: An `etree.Element` with the now DTD contextualized XML. 38 | """ 39 | root = None 40 | if hasattr(xml, 'getroot'): 41 | root = xml.getroot() 42 | else: 43 | root = xml.getroottree().getroot() 44 | 45 | tag = re.sub('\{.*\}', '', root.tag) 46 | preamble = DTD_PREAMBLE.format(root=tag) 47 | 48 | buff = io.BytesIO() 49 | buff.write(bytes(preamble, 'utf8')) 50 | buff.write(etree.tostring(xml, pretty_print=True, method='xml')) 51 | buff.seek(0) 52 | 53 | tree = etree.parse(buff) 54 | root = tree.getroot() 55 | 56 | return root 57 | 58 | 59 | def extract_signode(xml): 60 | """ Extracts the node from right under the root node in an XML document. If none 61 | is found there, an RuntimeException gets risen. 62 | 63 | :param `etree.Element` xml: Root node of the document. 64 | 65 | :return: `etree.Element` of the 66 | """ 67 | signode = xml.find('{http://www.w3.org/2000/09/xmldsig#}Signature') 68 | 69 | if signode is None: 70 | raise ValueError("Did not find a '{http://www.w3.org/2000/09/xmldsig#}Signature' under the root node") 71 | 72 | return signode 73 | 74 | 75 | def extract_signodes(xml): 76 | """ Extracts all nodes from given XML. 77 | 78 | :param `etree.Element` xml: Root node of the document. 79 | 80 | :return: list of `etree.Element` of the 's 81 | """ 82 | signodes = xml.xpath('//ds:Signature', namespaces={'ds': 'http://www.w3.org/2000/09/xmldsig#'}) 83 | return signodes 84 | 85 | 86 | def extract_signode_reference(signode): 87 | """ Extracts the of a node. 88 | 89 | :param `etree.Element` xml: Root node of the document. 90 | 91 | :return: `etree.Element` of the 92 | """ 93 | refs = signode.xpath('.//ds:Reference', namespaces={'ds': 'http://www.w3.org/2000/09/xmldsig#'}) 94 | 95 | if len(refs) != 1: 96 | raise ValueError("Could not find x509 reference on this signode") 97 | else: 98 | return refs[0] 99 | 100 | 101 | def extract_signode_certificate(signode): 102 | """ Extract the x509 Certificate Information from a . 103 | 104 | Raises exception if it does not find any information in the . 105 | 106 | :param `etree.Element` signode: Root node of the document. 107 | 108 | :return: UTF8 encoded string containing the base64 encoded PEM certificate in it. 109 | """ 110 | cert_node = signode.find('.//{http://www.w3.org/2000/09/xmldsig#}X509Certificate') 111 | cert_text = '' 112 | 113 | if cert_node is None: 114 | raise ValueError("Could not find x509 certificate on this signode") 115 | else: 116 | cert_text = cert_node.text 117 | 118 | buff = '-----BEGIN CERTIFICATE-----\n' 119 | buff += cert_text.strip('\n') 120 | buff += '\n-----END CERTIFICATE-----\n' 121 | 122 | return buff 123 | -------------------------------------------------------------------------------- /src/sii/lib/lib/__init__.py: -------------------------------------------------------------------------------- 1 | """ Third party Library static/copy includes. 2 | """ 3 | from . import fileio 4 | from . import format 5 | from . import output 6 | from . import syscall 7 | from . import xml 8 | 9 | 10 | __all__ = [ 11 | 'fileio', 12 | 'format', 13 | 'output', 14 | 'syscall', 15 | 'xml', 16 | ] 17 | -------------------------------------------------------------------------------- /src/sii/lib/lib/fileio.py: -------------------------------------------------------------------------------- 1 | """ Input/Output Functions. 2 | """ 3 | import os 4 | import shutil 5 | 6 | __all__ = [ 7 | 'read', 8 | 'read_create', 9 | 'write', 10 | 'write_safe' 11 | ] 12 | 13 | 14 | def read(path, encoding=None): 15 | """ Normal read from file after path user expansion. 16 | 17 | :param str path: Path of file from which from read contents. 18 | :param str encoding: Encoding with which to interpret the contents of the file. 19 | :returns: String with the files contents, decoded appropriately. 20 | """ 21 | path = os.path.abspath(os.path.expanduser(path)) 22 | 23 | with open(path, 'r', encoding=encoding) as fh: 24 | return fh.read() 25 | 26 | 27 | def read_create(path, templ_path, encoding=None): 28 | """ Reads file from path after expanding its user and location. If path is non-existant a copy 29 | of templ_path is copied over, and its contents returned instead. 30 | 31 | :param str path: Path to file to read from. 32 | :param str templ_path: Path to template file to copy onto path if path does not exist. 33 | :returns: String with the content of the file or templ_path file in the latter case. 34 | """ 35 | path = os.path.abspath(os.path.expanduser(path)) 36 | 37 | if not os.path.isfile(path): 38 | os.makedirs(os.path.split(path)[0], exist_ok=True) 39 | 40 | try: 41 | shutil.copyfile(templ_path, path) 42 | except FileNotFoundError as exc: 43 | raise FileNotFoundError( 44 | "Read/Create could not find or read template file at {0}".format(templ_path) 45 | ) from exc 46 | 47 | with open(path, 'r', encoding=encoding) as fh: 48 | return fh.read() 49 | 50 | def write(buff, path, encoding=None): 51 | """ Normal write to file after path user expansion. 52 | 53 | :param str buff: Unicode string with the contents to be written. 54 | :param str path: Path to file into which to write/create 55 | :param str encoding: Encoding of the buffer with which to write into file. 56 | """ 57 | path = os.path.abspath(os.path.expanduser(path)) 58 | 59 | with open(path, 'wb') as fh: 60 | fh.write(buff.encode(encoding)) 61 | 62 | 63 | def write_safe(buff, path): 64 | """ Writes to file in a safe manner, ensuring all or nothing thus barring 65 | from corruption. 66 | 67 | :param buff: Unicode encoded string. 68 | :param path: Path to write to 69 | """ 70 | tmp_fdir, tmp_fpath = os.path.split(path) 71 | tmp_fpath = ".{0}.new".format(tmp_fpath) 72 | tmp_fpath = os.path.join(tmp_fdir, tmp_fpath) 73 | 74 | with open(tmp_fpath, 'w') as fh: 75 | fh.write(buff) 76 | 77 | shutil.move(tmp_fpath, path) # swap files 78 | -------------------------------------------------------------------------------- /src/sii/lib/lib/format.py: -------------------------------------------------------------------------------- 1 | """ Data Formatters and related Utilities. 2 | """ 3 | import locale 4 | 5 | 6 | __all__ = [ 7 | 'thousands', 8 | 'rut' 9 | ] 10 | 11 | 12 | def thousands(value, monetary=False, zero='0'): 13 | string = '' 14 | 15 | if value == 0: 16 | string = str(zero) 17 | else: 18 | locale.setlocale(locale.LC_ALL, 'es_CL.utf8') # Hardset locale to Chile 19 | 20 | if isinstance(value, int): 21 | string = locale.format('%d', value, True, monetary) 22 | elif isinstance(value, float): 23 | string = locale.format('%.2f', value, True, monetary) 24 | else: 25 | raise TypeError("Don't know how to thousands format type: ".format(type(value))) 26 | 27 | return string 28 | 29 | 30 | def rut(base, ckdgt): 31 | """ Formats Chilean R.U.T. thousands separated and with the dash + modulo 11 checksum char. 32 | """ 33 | basenum = thousands(int(base)) 34 | result = '{0}-{1}'.format(basenum, ckdgt) 35 | 36 | return result 37 | -------------------------------------------------------------------------------- /src/sii/lib/lib/output.py: -------------------------------------------------------------------------------- 1 | """ Common Output and Printing Utilities 2 | """ 3 | from itertools import zip_longest 4 | 5 | __all__ = [ 6 | 'print_tabular', 7 | 8 | 'yellow', 9 | 'red', 10 | 'green', 11 | 'cyan', 12 | 'clear' 13 | ] 14 | 15 | 16 | def print_tabular(table, none_char="-"): 17 | """ Takes a OrderedDict and prints an ASCII (UTF-8 Encoded) Table with it like so: 18 | 19 | +------------------+------------+-----------------+..+ 20 | | Column 1 | Column2 | Column N | 21 | +------------------+------------+-----------------+..+ 22 | | Big Value | Stuff | - | 23 | | Looooooong Value | - | - | 24 | . . . . 25 | . . . . 26 | +------------------+------------+-----------------+..+ 27 | """ 28 | widths = _compute_widths(table) # Widest value per column 29 | 30 | # print header 31 | _print_widthed_delim(widths) 32 | _print_widthed_row(table.keys(), widths, none_char) 33 | _print_widthed_delim(widths) 34 | 35 | # print row by row 36 | for row in zip_longest(*table.values()): 37 | _print_widthed_row(row, widths, none_char) 38 | 39 | _print_widthed_delim(widths) # print closing hline 40 | 41 | 42 | def _compute_widths(table): 43 | widths = [] 44 | 45 | for column, value_list in table.items(): 46 | widest = len(str(column)) 47 | 48 | for value in value_list: 49 | if len(str(value)) > widest: 50 | widest = len(str(value)) 51 | 52 | widths.append(widest) 53 | 54 | return widths 55 | 56 | 57 | def _print_widthed_delim(widths, padding=2): 58 | row_str = "+" + "+".join(["-" * (width + padding) for width in widths]) + "+" 59 | 60 | print(row_str) 61 | 62 | 63 | def _print_widthed_row(row, widths, none_char): 64 | assert len(row) == len(widths), "You need one width per column to print this row" 65 | 66 | string = lambda pair: str(pair[0]) if pair[0] is not None else none_char 67 | 68 | row_str = " | ".join(["{0:<{1}}".format(string(pair), str(pair[1])) for pair in zip(row, widths)]) 69 | 70 | print("| ", end="") 71 | print(row_str, end="") 72 | print(" |", end="\n") 73 | 74 | 75 | STYLE_HEADER = chr(27) + '[95m' 76 | STYLE_CYAN = chr(27) + '[1;36m' 77 | STYLE_GREEN = chr(27) + '[1;32m' 78 | STYLE_WARNING = chr(27) + '[1;33m' 79 | STYLE_FAIL = chr(27) + '[1;31m' 80 | STYLE_BOLD = chr(27) + '[1m' 81 | STYLE_UNDERLINE = chr(27) + '[4m' 82 | STYLE_END = chr(27) + '[0m' 83 | 84 | 85 | def yellow(text): 86 | return "{0}{1}{2}".format(STYLE_WARNING, text, STYLE_END) 87 | 88 | 89 | def red(text): 90 | return "{0}{1}{2}".format(STYLE_FAIL, text, STYLE_END) 91 | 92 | 93 | def green(text): 94 | return "{0}{1}{2}".format(STYLE_GREEN, text, STYLE_END) 95 | 96 | 97 | def cyan(text): 98 | return "{0}{1}{2}".format(STYLE_CYAN, text, STYLE_END) 99 | 100 | 101 | def clear(): 102 | print(chr(27) + "[2J") 103 | print(chr(27) + "[H") 104 | -------------------------------------------------------------------------------- /src/sii/lib/lib/shell.py: -------------------------------------------------------------------------------- 1 | """ Shell Utility Imitiations or Wrappers. 2 | """ 3 | import os 4 | import traceback 5 | 6 | __all__ = [ 7 | 'which', 8 | 'cd' 9 | ] 10 | 11 | 12 | def which(program, fail=True): 13 | """ Sort of replicates the `which` utility. 14 | """ 15 | is_exe = lambda fp: os.path.isfile(fp) and os.access(fp, os.X_OK) 16 | locations = [os.path.join(path, program) for path in os.environ["PATH"].split(os.pathsep)] 17 | found = [loc for loc in locations if is_exe(loc)] 18 | 19 | if not found: 20 | if not fail: 21 | return False 22 | else: 23 | raise RuntimeError("Did not find program: <{0}>".format(program)) 24 | elif len(found) > 1: 25 | if not fail: 26 | return False 27 | else: 28 | raise RuntimeError("Found more than one instance of the program:\n" 29 | "{0}".format('\n'.join(found))) 30 | else: 31 | return found[0] 32 | 33 | 34 | class cd(object): 35 | """ Directory Context Switcher. 36 | 37 | Switches directory within the guards, switching back when leaving them. 38 | 39 | [NOT THREAD SAFE] 40 | If multiple threads switch around it looses its way back to the original 41 | working directory. A possible way would be to take into consideration in 42 | which thread it is currently being called. 43 | """ 44 | def __init__(self, dir): 45 | self.dir = dir 46 | self.olddir = None 47 | 48 | def __enter__(self): 49 | self._olddir = os.getcwd() 50 | os.chdir(self.dir) 51 | 52 | def __exit__(self, etype, evalue, etraceback): 53 | if any([etype, evalue, etraceback]): 54 | traceback.print_exception(etype, evalue, etraceback) 55 | os.chdir(self._olddir) 56 | -------------------------------------------------------------------------------- /src/sii/lib/lib/syscall.py: -------------------------------------------------------------------------------- 1 | """ Wrapping for all System Calls needed in this Library. 2 | 3 | TODO: 4 | * Break out lpstat stuff into own call. 5 | """ 6 | import os 7 | import re 8 | import tempfile 9 | import subprocess 10 | 11 | from .shell import which, cd 12 | 13 | 14 | __all__ = [ 15 | 'SystemCall', 16 | 17 | 'Ghostscript', 18 | 'Ps2Eps', 19 | 'PdfLaTeX', 20 | 'LP' 21 | ] 22 | 23 | 24 | class SystemCall(object): 25 | 26 | @property 27 | def name(self): 28 | """ Name of the Executable.. """ 29 | raise NotImplementedError 30 | 31 | @property 32 | def executable(self): 33 | """ Path to the Executable. """ 34 | raise NotImplementedError 35 | 36 | def check(self): 37 | """ Check if the Executable can be found and run. This is the point we have to 38 | fail if the program can not be found and/or is presumably not installed on the system. 39 | """ 40 | raise NotImplementedError 41 | 42 | def call(self): 43 | """ Call the executable to do something. Here the parameters and behaviours are 44 | implementation specific. 45 | 46 | NOTE: This function should default to hiding stdout and stderr from the callable. 47 | """ 48 | raise NotImplementedError 49 | 50 | 51 | class Ghostscript(SystemCall): 52 | 53 | @property 54 | def name(self): 55 | return 'ghostscript' 56 | 57 | @property 58 | def executable(self): 59 | return which(self.name) 60 | 61 | def check(self): 62 | exe = which(self.name, fail=False) 63 | return False if exe is False else True 64 | 65 | def call(self): 66 | raise NotImplementedError 67 | 68 | 69 | class Ps2Eps(SystemCall): 70 | 71 | @property 72 | def name(self): 73 | return 'ps2eps' 74 | 75 | @property 76 | def executable(self): 77 | return which(self.name) 78 | 79 | def check(self): 80 | exe = which(self.name, fail=False) 81 | return False if exe is False else True 82 | 83 | def call(self, filepath, force=False): 84 | """ 85 | :param filepath: Path to postscript file to convert to encapsulated postscript. 86 | :param force: Force overwrite if "eps" file does already exist. 87 | """ 88 | cmd = [self.executable, filepath] 89 | if force is True: 90 | cmd.insert(1, '-f') 91 | 92 | ret, out, err = _call(cmd, timeout=5) 93 | 94 | if ret != 0: 95 | raise RuntimeError( 96 | "Call <{0}> failed with exit code: <{1}> and output {2}" 97 | .format(cmd, ret, out) 98 | ) 99 | 100 | 101 | class PdfLaTeX(SystemCall): 102 | 103 | @property 104 | def name(self): 105 | return 'pdflatex' 106 | 107 | @property 108 | def executable(self): 109 | return which(self.name) 110 | 111 | def check(self): 112 | exe = which(self.name, fail=False) 113 | return False if exe is False else True 114 | 115 | def call(self, filename): 116 | """ 117 | :param filename: Path to TeX file to generate PDF from. 118 | 119 | NOTE: It has to switch directory during execution of `pdflatex`. Otherwise it will fail to 120 | find the resources referenced by the template. 121 | On a sidenote it is suggested you make a `tempdir` and copy the template and resources in 122 | there before running this over it. 123 | """ 124 | basedir, tex = os.path.split(filename) 125 | 126 | cmd = [self.executable, '--interaction batchmode', tex] 127 | ret, out, err = _call(cmd, timeout=5, cwd=basedir) 128 | 129 | if ret != 0: 130 | raise RuntimeError("Call \"{0}\" failed with exit code: <{1}>".format(" ".join(cmd), ret)) 131 | 132 | 133 | class LP(SystemCall): 134 | """ CUPS `lp` Command Line Interface. 135 | """ 136 | 137 | @property 138 | def name(self): 139 | return 'lp' 140 | 141 | @property 142 | def executable(self): 143 | return which(self.name) 144 | 145 | def check(self): 146 | exe = which(self.name, fail=False) 147 | return False if exe is False else True 148 | 149 | def call(self, filepath, printer=None): 150 | """ Print a file on a printer. 151 | 152 | :param filepath: Path to the file to be printed. 153 | :param printer: Name of the printer to print on. See `query_printers` to get a list. If None is provided 154 | the system default printer will be used. If there is none available, a ValueError will be 155 | thrown. 156 | """ 157 | cmd = ['lp'] 158 | 159 | if not printer: 160 | printer = self.query_printer_default() 161 | 162 | if not printer: 163 | raise ValueError("No printer provided and no default system printer to fallback to.") 164 | 165 | cmd.extend(['-d', printer]) # Specify the printer 166 | cmd.extend(['--', filepath]) # Specify the file 167 | 168 | code, output, error = _call(cmd, timeout=5) 169 | 170 | if not code == 0: 171 | raise RuntimeError("`lp` exited with non-zero: <{0}>".format(code)) 172 | 173 | def call_buff(self, data, printer=None): 174 | """ Print a buffer on a printer. 175 | 176 | :param bytes data: Data buffer to be printed. 177 | :param printer: Name of the printer to print on. See `query_printers` to get a list. 178 | """ 179 | if isinstance(data, str): 180 | data = bytes(data, 'UTF-8') 181 | 182 | with tempfile.NamedTemporaryFile() as tmp: 183 | tmp.file.write(data) 184 | tmp.file.flush() 185 | 186 | self.call(tmp.name, printer) 187 | 188 | def query_printers(self, only_ready=True): 189 | """ Return a list of available printers. 190 | 191 | :param bool only_ready: Appends a '-a' for available on the `ldstat` listing. 192 | 193 | :return: List of printer names as to be addressed on `ld`. 194 | """ 195 | printers = [] 196 | cmd = ['lpstat'] 197 | 198 | if only_ready: 199 | cmd.append('-a') 200 | 201 | code, output, error = _call(cmd, timeout=5) 202 | 203 | if not code == 0: 204 | raise RuntimeError("`lpstat` exited with error code: <{0}>".format(code)) 205 | 206 | if output: 207 | lines = output.split('\n') 208 | 209 | for line in lines: 210 | match = re.match('^(?P\w+) accepting requests since (?P[\w\s]+)', line) 211 | 212 | if match: 213 | attrs = match.groupdict() 214 | printers.append(attrs['name']) 215 | 216 | return printers 217 | 218 | def query_printer_default(self): 219 | """ Query which printer is set as default. 220 | 221 | :return: String with the name as to be addressed on `ld` or `None` if none is set on the 222 | system. 223 | """ 224 | printer = None 225 | cmd = ['lpstat', '-d'] 226 | 227 | code, output, error = _call(cmd, timeout=5) 228 | 229 | if not code == 0: 230 | raise RuntimeError("`lpstat` exited with error code: <{0}>".format(code)) 231 | 232 | if output: 233 | match = re.match('^system default destination: (?P[\w-]+)$', output) 234 | 235 | if match: 236 | printer = match.groupdict()['printer'] 237 | 238 | return printer 239 | 240 | 241 | def _call(args, timeout, cwd=None): 242 | proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) 243 | 244 | try: 245 | out, err = proc.communicate(timeout=timeout) 246 | except subprocess.TimeoutExpired: 247 | out, err = proc.communicate() 248 | 249 | ret = proc.returncode 250 | out = str(out, 'UTF-8') 251 | err = str(err, 'UTF-8') 252 | 253 | return ret, out, err 254 | -------------------------------------------------------------------------------- /src/sii/lib/lib/xml.py: -------------------------------------------------------------------------------- 1 | """ XML Proxy Class (helper) 2 | 3 | Requires External: 4 | * lxml 5 | """ 6 | import re 7 | import sys 8 | 9 | from lxml import etree 10 | 11 | 12 | __all__ = [ 13 | 'create_xml', 14 | 'read_xml', 15 | 'load_xml', 16 | 'wrap_xml', 17 | 'dump_etree', 18 | 'dump_xml', 19 | 'print_xml', 20 | 'write_xml' 21 | ] 22 | 23 | XML_DECL = lambda enc: b'' 24 | 25 | 26 | class XML(object): 27 | 28 | def __init__(self, node=None, name=None, text=None, namespaces=None): 29 | self._node = node if node is not None else etree.Element(name, nsmap=namespaces or {}) 30 | 31 | if name: 32 | self._node.tag = name 33 | 34 | if text is not None: 35 | self._node.text = str(text) 36 | 37 | for child in self._node.getchildren(): 38 | setattr(self, child.tag, XML(name=child.tag, node=child)) 39 | 40 | def __repr__(self): 41 | text = re.sub("^[\n\r\t\s]*", "", str(self)) 42 | text = re.sub("[\n\r\t\s]*$", "", text) 43 | return "".format(self.__name__, text) 44 | 45 | def __str__(self): 46 | if self._node.text is not None: 47 | return self._node.text 48 | else: 49 | return '' 50 | 51 | def __int__(self): 52 | if self._node.text is not None: 53 | return int(float(self._node.text)) 54 | else: 55 | return 0 56 | 57 | def __float__(self): 58 | if self._node.text is not None: 59 | return float(self._node.text) 60 | else: 61 | return 0.0 62 | 63 | def __iter__(self): 64 | for sibling in self.__generation__: 65 | if sibling.__name__ == self.__name__: 66 | yield sibling 67 | 68 | def __setattr__(self, name, value): 69 | attrname = re.sub('\{.*\}', '', name) 70 | 71 | # private methods 72 | if name.startswith('_'): 73 | super().__setattr__(attrname, value) 74 | return 75 | 76 | # children to append 77 | if isinstance(value, XML): 78 | super().__setattr__(attrname, value) 79 | self._node.append(value._node) 80 | return 81 | 82 | # children to modify or create if not yet existant 83 | try: 84 | node = getattr(self, name) 85 | node._node.text = str(value) 86 | except AttributeError: 87 | node = XML(name=name, text=value) 88 | super().__setattr__(attrname, node) 89 | self._node.append(node._node) 90 | 91 | def __getitem__(self, key): 92 | if isinstance(key, int): 93 | return self.__children__[key] 94 | return self._node.attrib[key] 95 | 96 | def __setitem__(self, key, value): 97 | self._node.attrib[key] = value 98 | 99 | def __remove__(self): 100 | parent = self._node.getparent() 101 | 102 | if len(parent): 103 | parent.remove(self._node) 104 | 105 | @property 106 | def __name__(self): 107 | return self._node.tag 108 | 109 | @property 110 | def __children__(self): 111 | return [XML(node=node) for node in self._node.getchildren()] 112 | 113 | @property 114 | def __siblings__(self): 115 | siblings = [] 116 | 117 | parent = self._node.getparent() 118 | if parent is not None: 119 | siblings.extend([XML(node=node) for node in parent.getchildren() if node is not self._node]) 120 | 121 | return siblings 122 | 123 | @property 124 | def __generation__(self): 125 | return self.__siblings__ + [self] 126 | 127 | @property 128 | def _etree(self): 129 | return self._node 130 | 131 | @property 132 | def _xml(self): 133 | return etree.tostring(self._node, encoding='unicode') 134 | # return etree.tostring(self._node) 135 | 136 | @property 137 | def _str(self): 138 | return str(self) 139 | 140 | @property 141 | def _int(self): 142 | return int(self) 143 | 144 | @property 145 | def _float(self): 146 | return float(self) 147 | 148 | @property 149 | def _number(self): 150 | value = float(self) 151 | if value % 1 != 0: 152 | return value 153 | else: 154 | return int(self) 155 | 156 | @property 157 | def _list(self): 158 | return list(self) 159 | 160 | def _has(self, name): 161 | return hasattr(self, name) 162 | 163 | 164 | def create_xml(name, value=None, namespaces=None): 165 | node = XML( 166 | name = name, 167 | text = value or None, 168 | namespaces = namespaces or {} 169 | ) 170 | 171 | return node 172 | 173 | 174 | def read_xml(path): 175 | doc = None 176 | root = None 177 | 178 | with open(path, "rb") as fh: 179 | doc = etree.parse(fh) 180 | 181 | root = doc.getroot() 182 | 183 | return XML(node=root) 184 | 185 | 186 | def load_xml(xml_string): 187 | root = etree.fromstring(xml_string) 188 | return XML(node=root) 189 | 190 | 191 | def wrap_xml(xml_etree): 192 | return XML(node=xml_etree) 193 | 194 | 195 | def dump_etree(xml_node): 196 | return xml_node._node 197 | 198 | 199 | def dump_xml(xml_node, **kwargs): 200 | if isinstance(xml_node, XML): 201 | xml = dump_etree(xml_node) 202 | else: 203 | xml = xml_node 204 | 205 | # Default encoding to UTF-8 206 | if not 'encoding' in kwargs: 207 | kwargs['encoding'] = 'UTF-8' 208 | 209 | # Replace/Fix XML declaration/preamble 210 | preamble = b"" 211 | xml_decl = kwargs.get('xml_declaration', False) 212 | if xml_decl: 213 | kwargs['xml_declaration'] = False 214 | 215 | preamble = XML_DECL(kwargs['encoding']) 216 | pretty = kwargs.get('pretty_print', False) 217 | if pretty: 218 | preamble += b"\n" 219 | 220 | buff = etree.tostring(xml, **kwargs) 221 | buff = preamble + buff 222 | 223 | return buff 224 | 225 | 226 | def print_xml(xml, file=sys.stdout.buffer, end='\n', encoding='UTF-8'): 227 | if isinstance(xml, XML): 228 | xml = dump_etree(xml) 229 | 230 | bytebuff = etree.tostring( 231 | xml, 232 | pretty_print = True, 233 | method = 'xml', 234 | encoding = encoding, 235 | xml_declaration = False 236 | ) 237 | 238 | encoded_end = bytes(end, encoding) 239 | file.write(XML_DECL(encoding) + encoded_end + bytebuff) 240 | 241 | 242 | def write_xml(xml, fpath, end='\n', encoding='UTF-8', append=False): 243 | mode = '' 244 | 245 | if append: 246 | mode = 'ab' 247 | else: 248 | mode = 'wb' 249 | 250 | with open(fpath, mode) as fh: 251 | bytebuff = etree.tostring( 252 | xml, 253 | pretty_print = True, 254 | method = 'xml', 255 | encoding = encoding, 256 | xml_declaration = False 257 | ) 258 | 259 | if end != '\n': 260 | decoded = str(bytebuff, encoding) 261 | modified = re.sub('\n', end, decoded) 262 | bytebuff = bytes(modified, encoding) 263 | 264 | fh.write(XML_DECL(encoding) + bytes(end, encoding) + bytebuff) 265 | -------------------------------------------------------------------------------- /src/sii/lib/printing/Document.py: -------------------------------------------------------------------------------- 1 | """ Containing Document Structure 2 | """ 3 | from sii.lib.lib import xml 4 | 5 | from .TemplateElement import TemplateElement 6 | 7 | 8 | class Document(TemplateElement): 9 | 10 | def __init__(self, doc_xml, cedible=False): 11 | self.doc_xml = doc_xml 12 | self.doc = None 13 | 14 | # Templating Globals 15 | self.tex_cedible = cedible 16 | 17 | # Document Globals 18 | self.doc_type = None 19 | self.doc_id = None 20 | 21 | # 52 - Guia Despacho 22 | self.doc_gd_type = None 23 | 24 | # TeX Sections 25 | self.preamble = None 26 | self.emitter = None 27 | self.sii_patch = None 28 | self.receiver = None 29 | self.items = None 30 | self.payments = None 31 | self.totals = None 32 | self.references = None 33 | self.barcode = None 34 | self.signature = None 35 | self.disclaimer = None 36 | 37 | # Init Hooks 38 | self.init_docvars() 39 | self.init_checks() 40 | 41 | def init_docvars(self): 42 | """ Extract Information, to be used by the individual sections once registered """ 43 | doc = xml.wrap_xml(self.doc_xml) 44 | 45 | self.doc_type = doc.Documento.Encabezado.IdDoc.TipoDTE._int 46 | self.doc_id = doc.Documento.Encabezado.IdDoc.Folio._int 47 | 48 | # Guia Despacho Specifics 49 | if self.doc_type == 52: 50 | self.doc_gd_type = doc.Documento.Encabezado.IdDoc.IndTraslado._int 51 | 52 | def init_checks(self): 53 | """ Run Global Assertions """ 54 | if self.tex_cedible: 55 | if self.doc_type in (56, 61): 56 | raise AssertionError("NC and ND are not subject to the CEDIBLE template") 57 | 58 | if self.doc_type == 52: 59 | non_cedibles = (2, 4, 5, 7, 8) 60 | 61 | if self.doc_gd_type in non_cedibles: 62 | raise AssertionError("GD of types {0} are not subject to a CEDIBLE template".format(non_cedibles)) 63 | 64 | @property 65 | def resources(self): 66 | resources = [] 67 | 68 | for name, attr in self.__dict__.items(): 69 | if isinstance(attr, TemplateElement): 70 | section = getattr(self, name) 71 | resources += section.resources 72 | 73 | return resources 74 | 75 | @property 76 | def carta(self): 77 | self._check() 78 | 79 | tex = self.preamble.carta 80 | 81 | tex += '\n' 82 | tex += '\n' 83 | 84 | tex += '\\begin{document}\n' 85 | 86 | # HEAD 87 | tex += '\\begin{minipage}[t]{0.6\\textwidth}\n' 88 | tex += self.emitter.carta 89 | tex += '\\end{minipage}%\n' 90 | tex += '\\begin{minipage}[t]{0.4\\textwidth}\n' 91 | tex += ' \\vspace{-2em}\n' 92 | tex += self.sii_patch.carta 93 | tex += ' \\vfill\n' 94 | tex += '\\end{minipage}%\n' 95 | tex += '\\vspace{2mm}\n' 96 | tex += self.receiver.carta 97 | 98 | # ITEMS 99 | tex += '\\begin{mdframed}[style=items]\n' 100 | tex += self.items.carta 101 | tex += '\\end{mdframed}\n' 102 | 103 | tex += '\\null\n' 104 | tex += '\\vfill\n' 105 | 106 | # FEFERENCES 107 | tex += '\\begin{mdframed}[style=references]\n' 108 | tex += self.references.carta 109 | tex += '\\end{mdframed}\n' 110 | 111 | # PAYMENTS/TOTALS 112 | tex += '\\begin{mdframed}[style=summary]\n' 113 | tex += ' \\vspace{-1em} % Zip them together (suppress spacing)\n' 114 | tex += ' \\begin{minipage}[t]{0.7\\textwidth}\n' 115 | tex += self.payments.carta 116 | tex += ' \\end{minipage}%\n' 117 | tex += ' \\begin{minipage}[t]{0.3\\textwidth}\n' 118 | tex += self.totals.carta 119 | tex += ' \\end{minipage}\n' 120 | tex += ' \\vspace{-1em} % Zip them together (suppress spacing)\n' 121 | tex += '\\end{mdframed}\n' 122 | 123 | # BARCODE 124 | tex += '\\begin{minipage}[t]{0.5\\textwidth}\n' 125 | tex += ' \\vfill\n' 126 | tex += self.barcode.carta 127 | tex += '\\end{minipage}%\n' 128 | 129 | # SIGNATURE (§19.983) 130 | tex += '\\begin{minipage}[t]{0.5\\textwidth}\n' 131 | tex += ' \\vfill\n' 132 | tex += self.signature.carta 133 | tex += '\\end{minipage}%\n' 134 | 135 | # DISCLAIMER 136 | tex += self.disclaimer.carta 137 | 138 | tex += '\\end{document}\n' 139 | return tex 140 | 141 | @property 142 | def oficio(self): 143 | self._check() 144 | 145 | tex = self.preamble.oficio 146 | 147 | tex += '\n' 148 | tex += '\n' 149 | 150 | tex += '\\begin{document}\n' 151 | 152 | # HEAD 153 | tex += '\\begin{minipage}[t]{0.6\\textwidth}\n' 154 | tex += self.emitter.oficio 155 | tex += '\\end{minipage}%\n' 156 | tex += '\\begin{minipage}[t]{0.4\\textwidth}\n' 157 | tex += ' \\vspace{-2em}\n' 158 | tex += self.sii_patch.oficio 159 | tex += ' \\vfill\n' 160 | tex += '\\end{minipage}%\n' 161 | tex += '\\vspace{2mm}\n' 162 | tex += self.receiver.oficio 163 | 164 | # ITEMS 165 | tex += '\\begin{mdframed}[style=items]\n' 166 | tex += self.items.oficio 167 | tex += '\\end{mdframed}\n' 168 | 169 | tex += '\\null\n' 170 | tex += '\\vfill\n' 171 | 172 | # FEFERENCES 173 | tex += '\\begin{mdframed}[style=references]\n' 174 | tex += self.references.oficio 175 | tex += '\\end{mdframed}\n' 176 | 177 | # PAYMENTS/TOTALS 178 | tex += '\\begin{mdframed}[style=summary]\n' 179 | tex += ' \\vspace{-1em} % Zip them together (suppress spacing)\n' 180 | tex += ' \\begin{minipage}[t]{0.7\\textwidth}\n' 181 | tex += self.payments.oficio 182 | tex += ' \\end{minipage}%\n' 183 | tex += ' \\begin{minipage}[t]{0.3\\textwidth}\n' 184 | tex += self.totals.oficio 185 | tex += ' \\end{minipage}\n' 186 | tex += ' \\vspace{-1em} % Zip them together (suppress spacing)\n' 187 | tex += '\\end{mdframed}\n' 188 | 189 | # BARCODE 190 | tex += '\\begin{minipage}[t]{0.5\\textwidth}\n' 191 | tex += ' \\vfill\n' 192 | tex += self.barcode.oficio 193 | tex += '\\end{minipage}%\n' 194 | 195 | # SIGNATURE (§19.983) 196 | tex += '\\begin{minipage}[t]{0.5\\textwidth}\n' 197 | tex += ' \\vfill\n' 198 | tex += self.signature.oficio 199 | tex += '\\end{minipage}%\n' 200 | 201 | # DISCLAIMER 202 | tex += self.disclaimer.oficio 203 | 204 | tex += '\\end{document}\n' 205 | return tex 206 | 207 | @property 208 | def thermal80mm(self): 209 | self._check() 210 | 211 | tex = self.preamble.thermal80mm 212 | 213 | tex += '\n' 214 | tex += '\n' 215 | 216 | tex += '\\begin{document}\n' 217 | 218 | tex += self.sii_patch.thermal80mm 219 | tex += '\\vspace{1mm}\n' 220 | tex += '\hrule\hrule\hrule\n' 221 | tex += self.emitter.thermal80mm 222 | tex += '\hrule\hrule\hrule\n' 223 | tex += self.receiver.thermal80mm 224 | tex += '\hrule\hrule\hrule\n' 225 | tex += self.items.thermal80mm 226 | tex += '\hrule\hrule\hrule\n' 227 | tex += self.totals.thermal80mm 228 | tex += '\hrule\hrule\hrule\n' 229 | tex += self.payments.thermal80mm 230 | tex += '\hrule\hrule\hrule\n' 231 | tex += self.references.thermal80mm 232 | tex += '\hrule\hrule\hrule\n' 233 | 234 | # FOOTER 235 | tex += self.signature.thermal80mm 236 | tex += self.barcode.thermal80mm 237 | tex += self.disclaimer.thermal80mm 238 | 239 | tex += '\\end{document}\n' 240 | return tex 241 | 242 | def set_preamble(self, preamble_section): 243 | self.preamble = preamble_section 244 | preamble_section.__document__ = self 245 | 246 | def set_emitter(self, emitter_section): 247 | self.emitter = emitter_section 248 | emitter_section.__document__ = self 249 | 250 | def set_sii_patch(self, sii_patch_section): 251 | self.sii_patch = sii_patch_section 252 | sii_patch_section.__document__ = self 253 | 254 | def set_receiver(self, receiver_section): 255 | self.receiver = receiver_section 256 | receiver_section.__document__ = self 257 | 258 | def set_items(self, items_section): 259 | self.items = items_section 260 | items_section.__document__ = self 261 | 262 | def set_payments(self, payments_section): 263 | self.payments = payments_section 264 | payments_section.__document__ = self 265 | 266 | def set_totals(self, totals_section): 267 | self.totals = totals_section 268 | totals_section.__document__ = self 269 | 270 | def set_references(self, references_section): 271 | self.references = references_section 272 | references_section.__document__ = self 273 | 274 | def set_barcode(self, barcode_section): 275 | self.barcode = barcode_section 276 | barcode_section.__document__ = self 277 | 278 | def set_signature(self, signature_section): 279 | self.signature = signature_section 280 | signature_section.__document__ = self 281 | 282 | def set_disclaimer(self, disclaimer_section): 283 | self.disclaimer = disclaimer_section 284 | disclaimer_section.__document__ = self 285 | 286 | def _check(self): 287 | errmsg = "" 288 | 289 | if not self.preamble: 290 | errmsg = "Missing 'Preamble' Section" 291 | 292 | if not self.emitter: 293 | errmsg = "Missing 'Emitter' Section" 294 | 295 | if not self.sii_patch: 296 | errmsg = "Missing 'Sii Patch' Section" 297 | 298 | if not self.receiver: 299 | errmsg = "Missing 'Receiver' Section" 300 | 301 | if not self.items: 302 | errmsg = "Missing 'Items' Section" 303 | 304 | if not self.payments: 305 | errmsg = "Missing 'Payments' Section" 306 | 307 | if not self.totals: 308 | errmsg = "Missing 'Totals' Section" 309 | 310 | if not self.references: 311 | errmsg = "Missing 'References' Section" 312 | 313 | if not self.barcode: 314 | errmsg = "Missing 'Barcode' Section" 315 | 316 | if not self.signature: 317 | errmsg = "Missing 'Signature' Section" 318 | 319 | if not self.disclaimer: 320 | errmsg = "Missing 'Disclaimer' Section" 321 | 322 | if errmsg: 323 | raise ValueError("Cannot create Template: " + errmsg) 324 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionBarcode.py: -------------------------------------------------------------------------------- 1 | """ Barcode Section of the Document 2 | 3 | Contains: 4 | * Barcode (PDF417) 5 | * Resolution Number 6 | * Resolution Date 7 | """ 8 | from .TemplateElement import TemplateElement, Resource 9 | from .barcode import PDF417 10 | 11 | 12 | class SectionBarcode(TemplateElement): 13 | """ 14 | %% ----------------------------------------------------------------- 15 | %% SECTION - Barcode 16 | %% ----------------------------------------------------------------- 17 | \\begin{center} 18 | \\includegraphics[width=%s\\textwidth]{barcode.eps} \\\\ 19 | \\scriptsize{ 20 | Timbre Electrónico SII \\\\ 21 | Res. %s del %s - Verifique documento: www.sii.cl 22 | } 23 | \\end{center} 24 | """ 25 | def __init__(self, data, resolution_number, resolution_datestr): 26 | self._data = data 27 | self._res_number = resolution_number 28 | self._res_datestr = resolution_datestr 29 | 30 | self._barcode = None 31 | 32 | @property 33 | def resources(self): 34 | ress = [] 35 | ress.append(Resource('barcode.eps', self._eps)) 36 | 37 | return ress 38 | 39 | @property 40 | def carta(self): 41 | tex = self.__doc__ % ( 42 | 0.9, 43 | self._res_number, 44 | self._res_datestr 45 | ) 46 | return tex 47 | 48 | @property 49 | def oficio(self): 50 | tex = self.__doc__ % ( 51 | 0.9, 52 | self._res_number, 53 | self._res_datestr 54 | ) 55 | return tex 56 | 57 | @property 58 | def thermal80mm(self): 59 | tex = self.__doc__ % ( 60 | 1.0, 61 | self._res_number, 62 | self._res_datestr 63 | ) 64 | return tex 65 | 66 | @property 67 | def _eps(self): 68 | if not self._barcode: 69 | pdf417 = PDF417(self._data) 70 | self._barcode = pdf417.eps 71 | return self._barcode 72 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionDisclaimer.py: -------------------------------------------------------------------------------- 1 | """ Disclaimer Section of the Document (you might want to subclass this one) 2 | 3 | Contains: 4 | * Company Name 5 | * Company Rut 6 | * Disclaimer of the Company (subclass if you want to change this) 7 | """ 8 | from .TemplateElement import TemplateElement 9 | 10 | __all__ = [ 11 | 'SectionDisclaimer', 12 | 'SectionDisclaimerDummy' 13 | ] 14 | 15 | 16 | class SectionDisclaimer(TemplateElement): 17 | """ 18 | %%%% ----------------------------------------------------------------- 19 | %%%% SECTION - Disclaimer 20 | %%%% ----------------------------------------------------------------- 21 | \\tiny{ 22 | %s\\ (%s) queda facultado(a) para informar y publicar en los registros 23 | o bancos de datos personales que operan en el país, u otras empresas con similares 24 | servicios, la mora o incumplimiento de las obligaciones expresadas en esta factura. 25 | } 26 | """ 27 | def __init__(self, company_name, company_rut): 28 | self._company_name = company_name 29 | self._company_rut = company_rut 30 | 31 | @property 32 | def carta(self): 33 | return self.__doc__ % (self._company_name, self._company_rut) 34 | 35 | @property 36 | def oficio(self): 37 | return self.__doc__ % (self._company_name, self._company_rut) 38 | 39 | @property 40 | def thermal80mm(self): 41 | return self.__doc__ % (self._company_name, self._company_rut) 42 | 43 | 44 | class SectionDisclaimerDummy(TemplateElement): 45 | """ 46 | %%%% ----------------------------------------------------------------- 47 | %%%% SECTION - Disclaimer 48 | %%%% ----------------------------------------------------------------- 49 | \\tiny{ 50 | Este documento ha sido emitido por una tercera parte, siendo esta impresion en papel 51 | generada por el receptor para fines internos de archivacion u otros. 52 | } 53 | """ 54 | def __init__(self): 55 | pass 56 | 57 | @property 58 | def carta(self): 59 | return self.__doc__ 60 | 61 | @property 62 | def oficio(self): 63 | return self.__doc__ 64 | 65 | @property 66 | def thermal80mm(self): 67 | return self.__doc__ 68 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionEmitter.py: -------------------------------------------------------------------------------- 1 | """ Emitter Section of the Document 2 | 3 | Contains: 4 | 5 | * Emitter Name (full Version) 6 | * Emitter Activity (Economic Role) 7 | * Emitter HQ Address String 8 | * Emitter emitting Branch String 9 | 10 | * (carta/oficio only) Logo (Optional) [takes the path to the logo EPS] 11 | 12 | * (thermal*mm only) Emitter Name (short Version) 13 | * (thermal*mm only) Emitter Salesman 14 | * (thermal*mm only) Order Number 15 | * (thermal*mm only) Licence Plate 16 | 17 | Comes in two flavours: 18 | 19 | * Emitter (emitting company is the same as the one printing the document) 20 | * Provider (emitting company is a provider for the one printing the document) 21 | """ 22 | import os.path as path 23 | 24 | from .TemplateElement import TemplateElement, Resource 25 | 26 | __all__ = [ 27 | 'SectionEmitter', 28 | 'SectionEmitterProvider' 29 | ] 30 | 31 | 32 | class SectionEmitter(TemplateElement): 33 | """ 34 | %% ----------------------------------------------------------------- 35 | %% SECTION - Emitter 36 | %% ----------------------------------------------------------------- 37 | \\begin{minipage}[t]{\\textwidth} 38 | \\begin{center} 39 | \\Large{\\textbf{%s}}\\break 40 | \\normalsize{%s} 41 | \\vspace{2mm} 42 | { 43 | \\extrarowsep=_-1pt^-1pt 44 | \\begin{tabu}{X[-1r] X[-1l]} 45 | \\rowfont{\\scriptsize} 46 | \\everyrow{\\rowfont{\\scriptsize}} 47 | \\textbf{CASA MATRIZ:} & %s \\\\ 48 | \\textbf{SUCURSAL EMISORA:} & %s \\\\ 49 | \\textbf{FONO:} & %s \\\\ 50 | \\end{tabu} 51 | } 52 | \\vspace{2mm} 53 | %s 54 | \\end{center} 55 | \\vfill 56 | \\end{minipage}%%%% 57 | """ 58 | def __init__(self, emitter_name_long, emitter_name_short, 59 | emitter_activity, 60 | emitter_hq_addr, emitter_branch_addr, emitter_phone, 61 | order_number='', emitter_salesman='', licence_plate='', 62 | logo_path=''): 63 | self._emitter_name_long = emitter_name_long 64 | self._emitter_name_short = emitter_name_short 65 | self._emitter_activity = emitter_activity 66 | self._emitter_hq_addr = emitter_hq_addr 67 | self._emitter_branch_addr = emitter_branch_addr 68 | self._emitter_phone = emitter_phone 69 | 70 | # carta/oficio specifics 71 | self._logo_path = logo_path 72 | if self._logo_path: 73 | with open(self._logo_path, 'rb') as fh: 74 | self._logo_data = fh.read() 75 | 76 | # thermal*mm specifics 77 | self._order_number = order_number 78 | self._licence_plate = licence_plate 79 | self._emitter_salesman = emitter_salesman 80 | 81 | @property 82 | def resources(self): 83 | ress = [] 84 | 85 | if self._logo_path: 86 | _, ext = path.splitext(self._logo_path) 87 | ress.append(Resource('logo' + ext, self._logo_data)) 88 | 89 | return ress 90 | 91 | @property 92 | def carta(self): 93 | return self.__doc__ % ( 94 | self._emitter_name_long, 95 | self._emitter_activity, 96 | self._emitter_hq_addr, 97 | self._emitter_branch_addr, 98 | self._emitter_phone, 99 | self._logo_template() 100 | ) 101 | 102 | @property 103 | def oficio(self): 104 | return self.__doc__ % ( 105 | self._emitter_name_long, 106 | self._emitter_activity, 107 | self._emitter_hq_addr, 108 | self._emitter_branch_addr, 109 | self._emitter_phone, 110 | self._logo_template() 111 | ) 112 | 113 | @property 114 | def thermal80mm(self): 115 | tex = """ 116 | { 117 | \\scriptsize 118 | \\tabulinesep=_1.0mm^1.0mm 119 | 120 | \\textbf{RAZON SOCIAL EMISOR:} \\\\ 121 | \\begin{tabu}{@{} X[-1l] X[l] @{}} 122 | \\textbf{Nombre:} & %s \\\\ 123 | \\textbf{Giro:} & %s \\\\ 124 | \\textbf{Casa Matriz:} & %s \\\\ 125 | \\textbf{Sucursal:} & %s \\\\ 126 | \\textbf{Fono:} & %s \\\\ 127 | \\textbf{N\\textdegree Pedido:} & %s \\\\ 128 | \\textbf{Vendedor:} & %s \\\\ 129 | \\textbf{Patente:} & %s \\\\ 130 | \\end{tabu} 131 | }%%%% 132 | """ 133 | return tex % ( 134 | self._emitter_name_short, 135 | self._emitter_activity, 136 | self._emitter_hq_addr, 137 | self._emitter_branch_addr, 138 | self._emitter_phone, 139 | self._order_number, 140 | self._emitter_salesman, 141 | self._licence_plate 142 | ) 143 | 144 | def _logo_template(self): 145 | if self._logo_path: 146 | _, ext = path.splitext(self._logo_path) 147 | 148 | height = '15mm' 149 | width = '0.7\\textwidth' 150 | keepratio = 'true' 151 | 152 | return '\\includegraphics[height={h}, width={w}, keepaspectratio={r}]{{logo{ext}}}'.format( 153 | h = height, 154 | w = width, 155 | r = keepratio, 156 | ext = ext 157 | ) 158 | else: 159 | return '' 160 | 161 | 162 | class SectionEmitterProvider(TemplateElement): 163 | """ 164 | %% ----------------------------------------------------------------- 165 | %% SECTION - Emitter 166 | %% ----------------------------------------------------------------- 167 | \\begin{minipage}[t]{\\textwidth} 168 | \\begin{center} 169 | \\Large{\\textbf{%s}}\\break 170 | \\normalsize{%s} 171 | \\vspace{2mm} 172 | { 173 | \\extrarowsep=_-1pt^-1pt 174 | \\begin{tabu}{X[-1r] X[-1l]} 175 | \\rowfont{\\scriptsize} 176 | \\everyrow{\\rowfont{\\scriptsize}} 177 | \\textbf{DIRECCION:} & %s \\\\ 178 | \\textbf{FONO:} & %s \\\\ 179 | \\end{tabu} 180 | } 181 | \\vspace{0.6cm} 182 | \\centerline{\\textbf{\\large --- DOCUMENTO PROVEEDOR ---}} 183 | \\end{center} 184 | \\vfill 185 | \\end{minipage}%%%% 186 | """ 187 | def __init__(self, emitter_name, emitter_activity, emitter_address, emitter_phone): 188 | self._emitter_name = emitter_name 189 | self._emitter_activity = emitter_activity 190 | self._emitter_address = emitter_address 191 | self._emitter_phone = emitter_phone 192 | 193 | @property 194 | def carta(self): 195 | return self.__doc__ % ( 196 | self._emitter_name, 197 | self._emitter_activity, 198 | self._emitter_address, 199 | self._emitter_phone 200 | ) 201 | 202 | @property 203 | def oficio(self): 204 | return self.__doc__ % ( 205 | self._emitter_name, 206 | self._emitter_activity, 207 | self._emitter_address, 208 | self._emitter_phone 209 | ) 210 | 211 | @property 212 | def thermal80mm(self): 213 | tex = """ 214 | { 215 | \\scriptsize 216 | \\tabulinesep=_1.0mm^1.0mm 217 | 218 | \\textbf{RAZON SOCIAL EMISOR:} \\\\ 219 | \\begin{tabu}{@{} X[-1l] X[l] @{}} 220 | \\textbf{Nombre:} & %s \\\\ 221 | \\textbf{Giro:} & %s \\\\ 222 | \\textbf{Direccion:} & %s \\\\ 223 | \\textbf{Fono:} & %s \\\\ 224 | \\end{tabu} 225 | \\centerline{\\textbf{\\large --- DOCUMENTO PROVEEDOR ---}} 226 | }%%%% 227 | """ 228 | return tex % ( 229 | self._emitter_name, 230 | self._emitter_activity, 231 | self._emitter_address, 232 | self._emitter_phone 233 | ) 234 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionItems.py: -------------------------------------------------------------------------------- 1 | """ Items Section of the Document 2 | 3 | Contains: 4 | * Header Tags 5 | * Item Rows 6 | """ 7 | from .helpers import escape_tex 8 | 9 | from .TemplateElement import TemplateElement 10 | 11 | 12 | GUIA_DESPACHO_TYPES = { 13 | 1: "OPERACIÓN CONSTITUYE VENTA", 14 | 2: "VENTA POR EFECTUAR", 15 | 3: "CONSIGNACION", 16 | 4: "ENTREGA GRATUITA", 17 | 5: "TRASLADO INTERNO", 18 | 6: "OTRO TRASLADOS NO VENTA", 19 | 7: "GUÍA DE DEVOLUCIÓN", 20 | 8: "TRASLADO PARA EXPORTACIÓN. (NO VENTA)", 21 | 9: "VENTA PARA EXPORTACIÓN" 22 | } 23 | 24 | 25 | class SectionItems(TemplateElement): 26 | """ 27 | %%%% ----------------------------------------------------------------- 28 | %%%% SECTION - Items 29 | %%%% ----------------------------------------------------------------- 30 | { 31 | %%%% ITEM TABLE 32 | \\begin{longtabu}{%s} 33 | %%%% HEADER 34 | \\rowfont{\\%s} 35 | \\everyrow{\\rowfont{\\%s}} 36 | %s 37 | 38 | \\firsthline[1mm] 39 | 40 | %%%% CONTENT 41 | %s 42 | \\end{longtabu} 43 | 44 | %s 45 | } 46 | """ 47 | def __init__(self, column_layout, table_margins=False, draft=False, provider=False): 48 | """ Before using this Object you need to determine how the Columns are named, aligned and 49 | if they are expanding or not. 50 | You do that by providing the `column_layout` argument in the following Format: 51 | 52 | ( 53 | {'name': 'Colname1', 'align': 'left'|'right'|'center', expand: True|False}, 54 | {'name': 'Colname2', 'align': 'left'|'right'|'center', expand: True|False}, 55 | {'name': 'Colname3', 'align': 'left'|'right'|'center', expand: True|False}, 56 | ... 57 | ) 58 | 59 | For proper LaTeX results it is recomended to make all but one Column non-expanding. This 60 | way the expanding one will take up as much space as possible without making spacing look 61 | ugly, but also ensuring everything looks not so overy spread (specially the numeric 62 | columns) 63 | """ 64 | self._colsettings = column_layout 65 | self._items = [] 66 | self._table_margins = table_margins 67 | self._draft = draft 68 | self._provider = provider 69 | 70 | self.__doc__ = self.__doc__ % ( 71 | self._build_tablecols(), 72 | '%s', '%s', '%s', '%s', '%s' 73 | ) 74 | 75 | def append_row(self, row: tuple): 76 | """ Expecting the column tuple to be in the expected order the column layout was set up at 77 | init time. 78 | """ 79 | if len(row) != len(self._colsettings): 80 | raise ValueError( 81 | "Rows have to provide one value per set up Column:\n" 82 | ">{0}<\n" 83 | ">{1}<".format( 84 | ', '.join([col for col in row]), 85 | ', '.join([col['name'] for col in self._colsettings]) 86 | ) 87 | ) 88 | else: 89 | self._items.append(row) 90 | 91 | @property 92 | def carta(self): 93 | return self.__doc__ % ( 94 | 'small', 'footnotesize', 95 | self._build_headers(), 96 | self._build_rows(), 97 | self._build_disclaimer() 98 | 99 | ) 100 | 101 | @property 102 | def oficio(self): 103 | return self.__doc__ % ( 104 | 'small', 'footnotesize', 105 | self._build_headers(), 106 | self._build_rows(), 107 | self._build_disclaimer() 108 | ) 109 | 110 | @property 111 | def thermal80mm(self): 112 | return self.__doc__ % ( 113 | 'scriptsize', 'scriptsize', 114 | self._build_headers(), 115 | self._build_rows(), 116 | self._build_disclaimer() 117 | ) 118 | 119 | def _build_tablecols(self): 120 | cols = [] 121 | cols.append('' if self._table_margins else '@{}') 122 | 123 | for coldef in self._colsettings: 124 | setstr = 'X[' 125 | setstr += {True: '', False: '-1'}[coldef['expand']] 126 | setstr += {'left': 'l', 'center': 'c', 'right': 'r'}[coldef['align']] 127 | setstr += ']' 128 | cols.append(setstr) 129 | 130 | cols.append('' if self._table_margins else '@{}') 131 | return ' '.join(cols) 132 | 133 | def _build_headers(self): 134 | cols = ['\\textbf{%s}' % escape_tex(col['name']) for col in self._colsettings] 135 | tex = (' & \n' + ' ' * 4 * 3).join(cols) 136 | tex += ' \\\\\n' 137 | 138 | return tex 139 | 140 | def _build_rows(self): 141 | rows = [] 142 | 143 | for row in self._items: 144 | stringed = (str(col) for col in row) 145 | escaped = (escape_tex(colstr) for colstr in stringed) 146 | 147 | rows.append(' & '.join(escaped) + ' \\\\') 148 | 149 | return '\n'.join(rows) 150 | 151 | def _build_disclaimer(self): 152 | assert self.__document__, "Have not been yet registered onto a Document" 153 | 154 | disclaimers = [] 155 | doc_gd_type = self.__document__.doc_gd_type 156 | 157 | if doc_gd_type: 158 | disclaimers.append('\\centerline{\\textbf{\\large --- %s ---}}' % GUIA_DESPACHO_TYPES[doc_gd_type]) 159 | 160 | if self._draft: 161 | disclaimers.append('\\centerline{\\textbf{\\large --- %s ---}}' % "BORRADOR") 162 | 163 | if self._provider: 164 | disclaimers.append('\\centerline{\\textbf{\\large --- %s ---}}' % "DOCUMENTO PROVEEDOR") 165 | 166 | return "\n".join(disclaimers) 167 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionPayments.py: -------------------------------------------------------------------------------- 1 | """ Payments Section of the Document 2 | 3 | Contains: 4 | * Payment Mode/Type 5 | * Amount 6 | * Descriptor (Payment Detail / further Information / Description) 7 | """ 8 | from .TemplateElement import TemplateElement 9 | 10 | 11 | class SectionPayments(TemplateElement): 12 | """ 13 | %%%% ----------------------------------------------------------------- 14 | %%%% SECTION - Payments 15 | %%%% ----------------------------------------------------------------- 16 | { 17 | \extrarowsep=^1pt_1pt 18 | 19 | \\begin{longtabu}{%s} 20 | %%%% HEADER 21 | \\rowfont{\\%s} 22 | \everyrow{\\rowfont{\\%s}} 23 | \\textbf{Tipo Pago} & 24 | \\textbf{Monto[\$]} & 25 | \\textbf{Detalle} \\\\ 26 | 27 | %%%% DATA 28 | \\rowfont{\\%s} 29 | \everyrow{\\rowfont{\\%s}} 30 | %s 31 | \end{longtabu} 32 | } 33 | """ 34 | def __init__(self, table_margins=False): 35 | self._payments = [] 36 | 37 | self._table_margins = table_margins 38 | 39 | def append_payment(self, mode, amount, detail): 40 | self._payments.append((mode, amount, detail)) 41 | 42 | @property 43 | def carta(self): 44 | return self.__doc__ % ( 45 | self._build_tablecols(), 46 | 'small', 47 | 'small', 48 | 'footnotesize', 49 | 'footnotesize', 50 | self._build_payments() 51 | ) 52 | 53 | @property 54 | def oficio(self): 55 | return self.__doc__ % ( 56 | self._build_tablecols(), 57 | 'small', 58 | 'small', 59 | 'footnotesize', 60 | 'footnotesize', 61 | self._build_payments() 62 | ) 63 | 64 | @property 65 | def thermal80mm(self): 66 | return self.__doc__ % ( 67 | self._build_tablecols(), 68 | 'scriptsize', 69 | 'scriptsize', 70 | 'scriptsize', 71 | 'scriptsize', 72 | self._build_payments() 73 | ) 74 | 75 | def _build_tablecols(self): 76 | return 'X[-1l] X[-1r] X[l]' if self._table_margins else '@{} X[-1l] X[-1r] X[l] @{}' 77 | 78 | def _build_payments(self): 79 | pays = [] 80 | for pay in self._payments: 81 | pays.append('{0} & {1} & {2} \\\\'.format(*pay)) 82 | 83 | if pays: 84 | return ('\n' + ' ' * 4 * 3).join(pays) 85 | else: 86 | return '' 87 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionPreamble.py: -------------------------------------------------------------------------------- 1 | """ LaTeX document configuration preamble 2 | """ 3 | import sys 4 | 5 | from .TemplateElement import TemplateElement 6 | 7 | 8 | class SectionPreamble(TemplateElement): 9 | """ 10 | %% ----------------------------------------------------------------- 11 | %% SECTION - Preamble 12 | %% ----------------------------------------------------------------- 13 | \\documentclass[{options}]{{article}} 14 | 15 | \\usepackage[paperwidth={width}mm,% 16 | paperheight={height}mm,% 17 | top={top}mm,% 18 | bottom={bottom}mm,% 19 | left={left}mm,% 20 | right={right}mm]{{geometry}} 21 | \\usepackage[utf8]{{inputenc}} 22 | \\usepackage[spanish]{{babel}} 23 | \\usepackage[parfill]{{parskip}} 24 | \\usepackage{{setspace}} 25 | \\usepackage{{textcomp}} 26 | \\usepackage{{mdframed}} 27 | \\usepackage{{longtable}} 28 | \\usepackage{{tabu}} 29 | \\usepackage{{graphicx}} 30 | 31 | \\pagestyle{{empty}} 32 | 33 | """ 34 | 35 | def __init__(self, size=10, draft=False): 36 | self.size = size 37 | self.draft = draft 38 | 39 | @property 40 | def carta(self): 41 | tex = self.__doc__.format( 42 | options = self._doc_options(), 43 | width = 216, 44 | height = 279, 45 | top = 8, 46 | bottom = 5, 47 | left = 10, 48 | right = 10 49 | ) 50 | 51 | tex += """ 52 | \\mdfdefinestyle{siipatch}{nobreak=true ,% 53 | userdefinedwidth=8cm ,% 54 | align=center ,% 55 | linewidth=0.8mm ,% 56 | innerleftmargin=0.5cm ,% 57 | innerrightmargin=0.5cm} 58 | 59 | \\mdfdefinestyle{emitter}{nobreak=true ,% 60 | leftline=false ,% 61 | rightline=false ,% 62 | linewidth=0.5mm ,% 63 | skipabove=1mm ,% 64 | innertopmargin=0mm ,% 65 | innerbottommargin=0mm ,% 66 | innerleftmargin=0cm ,% 67 | innerrightmargin=0cm} 68 | 69 | \\mdfdefinestyle{items}{nobreak=true ,% 70 | topline=false ,% 71 | leftline=false ,% 72 | rightline=false ,% 73 | bottomline=false ,% 74 | linewidth=0.5mm ,% 75 | innertopmargin=0mm ,% 76 | innerbottommargin=0mm ,% 77 | innerleftmargin=0cm ,% 78 | innerrightmargin=0cm} 79 | 80 | \\mdfdefinestyle{summary}{nobreak=true ,% 81 | leftline=false ,% 82 | rightline=false ,% 83 | bottomline=false ,% 84 | linewidth=0.5mm ,% 85 | innertopmargin=0mm ,% 86 | innerbottommargin=0mm ,% 87 | innerleftmargin=0cm ,% 88 | innerrightmargin=0cm} 89 | 90 | \\mdfdefinestyle{references}{nobreak=true ,% 91 | topline=false ,% 92 | leftline=false ,% 93 | rightline=false ,% 94 | bottomline=false ,% 95 | linewidth=0.5mm ,% 96 | innertopmargin=0mm ,% 97 | innerbottommargin=0mm ,% 98 | innerleftmargin=0cm ,% 99 | innerrightmargin=0cm} 100 | 101 | \\mdfdefinestyle{signature}{nobreak=true ,% 102 | linewidth=0.5mm ,% 103 | innertopmargin=0.5cm} 104 | """ 105 | return tex 106 | 107 | @property 108 | def oficio(self): 109 | tex = self.__doc__.format( 110 | options = self._doc_options(), 111 | width = 216, 112 | height = 330, 113 | top = 8, 114 | bottom = 5, 115 | left = 10, 116 | right = 10 117 | ) 118 | 119 | tex += """ 120 | \\mdfdefinestyle{siipatch}{nobreak=true ,% 121 | userdefinedwidth=8cm ,% 122 | align=center ,% 123 | linewidth=0.8mm ,% 124 | innerleftmargin=0.5cm ,% 125 | innerrightmargin=0.5cm} 126 | 127 | \\mdfdefinestyle{emitter}{nobreak=true ,% 128 | leftline=false ,% 129 | rightline=false ,% 130 | linewidth=0.5mm ,% 131 | skipabove=1mm ,% 132 | innertopmargin=0mm ,% 133 | innerbottommargin=0mm ,% 134 | innerleftmargin=0cm ,% 135 | innerrightmargin=0cm} 136 | 137 | \\mdfdefinestyle{items}{nobreak=true ,% 138 | topline=false ,% 139 | leftline=false ,% 140 | rightline=false ,% 141 | bottomline=false ,% 142 | linewidth=0.5mm ,% 143 | innertopmargin=0mm ,% 144 | innerbottommargin=0mm ,% 145 | innerleftmargin=0cm ,% 146 | innerrightmargin=0cm} 147 | 148 | \\mdfdefinestyle{summary}{nobreak=true ,% 149 | leftline=false ,% 150 | rightline=false ,% 151 | bottomline=false ,% 152 | linewidth=0.5mm ,% 153 | innertopmargin=0mm ,% 154 | innerbottommargin=0mm ,% 155 | innerleftmargin=0cm ,% 156 | innerrightmargin=0cm} 157 | 158 | \\mdfdefinestyle{references}{nobreak=true ,% 159 | topline=false ,% 160 | leftline=false ,% 161 | rightline=false ,% 162 | bottomline=false ,% 163 | linewidth=0.5mm ,% 164 | innertopmargin=0mm ,% 165 | innerbottommargin=0mm ,% 166 | innerleftmargin=0cm ,% 167 | innerrightmargin=0cm} 168 | 169 | \\mdfdefinestyle{signature}{nobreak=true ,% 170 | linewidth=0.5mm ,% 171 | innertopmargin=0.5cm} 172 | """ 173 | return tex 174 | 175 | @property 176 | def thermal80mm(self): 177 | # XXX we have a problem with properly controlling the rasterization of EPSON T-88V printers 178 | # thus we have to cheat like this. (TODO: do properly, this is to generic. Not all printers 179 | # and drivers under CUPS have the same problem) 180 | if sys.platform in ('linux2', 'darwin'): 181 | left, right = 0, 16 182 | else: 183 | left = right = 5 184 | 185 | tex = self.__doc__.format( 186 | options = self._doc_options(), 187 | width = 80, 188 | height = 297, 189 | top = 0, 190 | bottom = 0, 191 | left = left, 192 | right = right 193 | ) 194 | 195 | tex += """ 196 | \\mdfdefinestyle{siipatch}{nobreak=true ,% 197 | align=center ,% 198 | linewidth=0.8mm ,% 199 | innerleftmargin=0.5cm ,% 200 | innerrightmargin=0.5cm} 201 | 202 | \\mdfdefinestyle{signature}{nobreak=true ,% 203 | linewidth=0.3mm ,% 204 | skipabove=1mm ,% 205 | innertopmargin=0.3cm ,% 206 | innerleftmargin=1mm ,% 207 | innerrightmargin=1mm} 208 | """ 209 | return tex 210 | 211 | def _doc_options(self): 212 | s = '{size}pt'.format(size=self.size) 213 | s += ',draft' if self.draft is True else '' 214 | 215 | return s 216 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionReceiver.py: -------------------------------------------------------------------------------- 1 | """ Receiver Section in the Document 2 | 3 | Contains: 4 | * Emission Date 5 | * Expiration Date 6 | * Receiver Name 7 | * Receiver RUT 8 | * Receiver Activity (Economic Role) 9 | * Receiver Address 10 | * Receiver Comune 11 | * Receiver City 12 | * (optional)(carta/oficio only) Emitter Salesman 13 | * (optional)(carta/oficio only) Order Number 14 | * (optional)(carta/oficio only) Licence Plate 15 | """ 16 | from .helpers import escape_tex 17 | 18 | from .TemplateElement import TemplateElement 19 | 20 | 21 | class SectionReceiver(TemplateElement): 22 | """ 23 | %% ----------------------------------------------------------------- 24 | %% SECTION - Receiver 25 | %% ----------------------------------------------------------------- 26 | \\begin{minipage}{0.5\\textwidth} 27 | \\begin{flushleft} 28 | \\small{\\textbf{Emisión:} %s} 29 | \\end{flushleft} 30 | \\end{minipage}%%%% 31 | \\begin{minipage}{0.5\\textwidth} 32 | \\begin{flushright} 33 | \\small{\\textbf{Vencimiento:} %s} 34 | \\end{flushright} 35 | \\end{minipage} 36 | 37 | \\begin{mdframed}[style=emitter] 38 | { 39 | \\tabulinesep=_1.0mm^1.0mm 40 | \\vspace{1mm} 41 | 42 | \\begin{tabu}{X[-1r] X[-1l] X[-1r] X[-1l]} 43 | \\rowfont{\\footnotesize} 44 | \\everyrow{\\rowfont{\\footnotesize}} 45 | \\textbf{SEÑOR(ES):} & %s & 46 | \\textbf{R.U.T.:} & %s \\\\ 47 | \\textbf{DIRECCION:} & %s & 48 | \\textbf{COMUNA:} & %s \\\\ 49 | \\textbf{GIRO:} & %s & 50 | \\textbf{CIUDAD:} & %s \\\\ 51 | \\hline 52 | \\textbf{VENDEDOR:} & %s & 53 | \\textbf{PATENTE:} & %s \\\\ 54 | \\textbf{N\\textdegree PEDIDO:} & %s & 55 | & \\\\ 56 | \\end{tabu} 57 | } 58 | \\end{mdframed} 59 | """ 60 | def __init__(self, emission_date, expiration_date, 61 | receivername, receiverrut, receiveraddress, 62 | receivercomune, receiveractivity, receivercity='', 63 | emittersalesman='', ordernumber='', licenceplate=''): 64 | self._emission_date = emission_date 65 | self._expiration_date = expiration_date 66 | 67 | self._receivername = escape_tex(receivername) 68 | self._receiverrut = receiverrut 69 | self._receiveraddress = escape_tex(receiveraddress) 70 | self._receivercomune = escape_tex(receivercomune) 71 | self._receiveractivity = escape_tex(receiveractivity) 72 | self._receivercity = escape_tex(receivercity) 73 | 74 | self._emittersalesman = escape_tex(emittersalesman) 75 | self._ordernumber = ordernumber 76 | self._licenceplate = licenceplate 77 | 78 | @property 79 | def carta(self): 80 | return self.__doc__ % ( 81 | self._emission_date, 82 | self._expiration_date, 83 | self._receivername, 84 | self._receiverrut, 85 | self._receiveraddress, 86 | self._receivercomune, 87 | self._receiveractivity, 88 | self._receivercity, 89 | self._emittersalesman, 90 | self._ordernumber, 91 | self._licenceplate 92 | ) 93 | 94 | @property 95 | def oficio(self): 96 | return self.__doc__ % ( 97 | self._emission_date, 98 | self._expiration_date, 99 | self._receivername, 100 | self._receiverrut, 101 | self._receiveraddress, 102 | self._receivercomune, 103 | self._receiveractivity, 104 | self._receivercity, 105 | self._emittersalesman, 106 | self._ordernumber, 107 | self._licenceplate 108 | ) 109 | 110 | @property 111 | def thermal80mm(self): 112 | tex = """ 113 | { 114 | \\scriptsize 115 | \\tabulinesep=_1.0mm^1.0mm 116 | 117 | \\textbf{RAZON SOCIAL RECEPTOR:} \\\\ 118 | \\begin{tabu}{@{} X[-1l] X[l] @{}} 119 | \\textbf{Señor(es):} & %s \\\\ 120 | \\textbf{R.U.T.:} & %s \\\\ 121 | \\textbf{Giro:} & %s \\\\ 122 | \\textbf{Dirección:} & %s \\\\ 123 | \\textbf{Comuna:} & %s \\\\ 124 | \\textbf{Ciudad:} & %s \\\\ 125 | \\end{tabu} 126 | } 127 | """ 128 | return tex % ( 129 | self._receivername, 130 | self._receiverrut, 131 | self._receiveractivity, 132 | self._receiveraddress, 133 | self._receivercomune, 134 | self._receivercity 135 | ) 136 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionReferences.py: -------------------------------------------------------------------------------- 1 | """ References Section of the Document 2 | 3 | Contains: 4 | * Document Type 5 | * Document Serial Number 6 | * Document Date 7 | * Reason of Reference 8 | """ 9 | from .TemplateElement import TemplateElement 10 | 11 | 12 | DOC_TYPE_STRINGS = { 13 | 30 : "FACTURA", 14 | 32 : "FACTURA NO AFECTA O EXENTA", 15 | 33 : "FACTURA ELECTRÓNICA", 16 | 34 : "FACTURA NO AFECTA O EXENTA ELECTRÓNICA", 17 | 35 : "BOLETA", 18 | 38 : "BOLETA EXENTA", 19 | 39 : "BOLETA ELECTRÓNICA", 20 | 40 : "LIQUIDACIÓN FACTURA", 21 | 41 : "BOLETA EXENTA ELECTRÓNICA", 22 | 43 : "LIQUIDACIÓN FACTURA ELECTRÓNICA", 23 | 45 : "FACTURA DE COMPRA", 24 | 46 : "FACTURA DE COMPRA ELECTRÓNICA", 25 | 50 : "GUÍA DE DESPACHO.", 26 | 52 : "GUÍA DE DESPACHO ELECTRÓNICA", 27 | 55 : "NOTA DE DÉBITO", 28 | 56 : "NOTA DE DÉBITO ELECTRÓNICA", 29 | 60 : "NOTA DE CRÉDITO", 30 | 61 : "NOTA DE CRÉDITO ELECTRÓNICA", 31 | 103 : "LIQUIDACIÓN", 32 | 110 : "FACTURA DE EXPORTACIÓN ELECTRÓNICA", 33 | 111 : "NOTA DE DÉBITO DE EXPORTACIÓN ELECTRÓNICA", 34 | 112 : "NOTA DE CRÉDITO DE EXPORTACIÓN ELECTRÓNICA", 35 | 801 : "ORDEN DE COMPRA", 36 | 802 : "NOTA DE PEDIDO", 37 | 803 : "CONTRATO", 38 | 804 : "RESOLUCIÓN", 39 | 805 : "PROCESO CHILECOMPRA", 40 | 806 : "FICHA CHILECOMPRA", 41 | 807 : "DUS", 42 | 808 : "B/L (CONOCIMIENTO DE EMBARQUE)", 43 | 809 : "AWB (AIR WILL BILL)", 44 | 810 : "MIC/DTA", 45 | 811 : "CARTA DE PORTE", 46 | 812 : "RESOLUCIÓN DEL SNA DONDE CALIFICA SERVICIOS DE EXPORTACIÓN", 47 | 813 : "PASAPORTE", 48 | 814 : "CERTIFICADO DE DEPÓSITO BOLSA PROD. CHILE.", 49 | 815 : "VALE DE PRENDA BOLSA PROD. CHILE", 50 | 'SET': "SET" 51 | } 52 | 53 | 54 | class SectionReferences(TemplateElement): 55 | """ 56 | %% ----------------------------------------------------------------- 57 | %% SECTION - References 58 | %% ----------------------------------------------------------------- 59 | { 60 | %%%% ITEM TABLE 61 | \\begin{longtabu}{@{} X[-1l] X[l] X[-1r] X[-1r] X[-1r] @{}} 62 | %%%% HEADER 63 | \\rowfont{\\%s} 64 | \\everyrow{\\rowfont{\\%s}} 65 | \\textbf{Nro.} & 66 | \\textbf{Razón} & 67 | \\textbf{Tipo} & 68 | \\textbf{Folio} & 69 | \\textbf{Fecha} \\\\ 70 | 71 | %%\\tabucline{1-4} 72 | \\firsthline[1mm] 73 | 74 | %%%% CONTENT 75 | %s 76 | \\end{longtabu} 77 | } 78 | """ 79 | def __init__(self): 80 | self._refs = [] 81 | 82 | def append_reference(self, reason, index, dte_type, dte_serial, dte_date): 83 | self._refs.append(( 84 | index, 85 | reason, 86 | DOC_TYPE_STRINGS[dte_type], 87 | dte_serial, 88 | dte_date 89 | )) 90 | 91 | @property 92 | def carta(self): 93 | return self.__doc__ % ( 94 | 'small', 95 | 'footnotesize', 96 | self._build_references(), 97 | ) 98 | 99 | @property 100 | def oficio(self): 101 | return self.__doc__ % ( 102 | 'small', 103 | 'footnotesize', 104 | self._build_references() 105 | ) 106 | 107 | @property 108 | def thermal80mm(self): 109 | return self.__doc__ % ( 110 | 'scriptsize', 111 | 'scriptsize', 112 | self._build_references() 113 | ) 114 | 115 | def _build_references(self): 116 | refs = [] 117 | 118 | for ref in self._refs: 119 | refs.append('{0} & {1} & {2} & {3} & {4}\\\\'.format(*ref)) 120 | 121 | if refs: 122 | return ('\n' + ' ' * 4 * 3).join(refs) 123 | else: 124 | return '– sin referencias –' 125 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionSignature.py: -------------------------------------------------------------------------------- 1 | """ Signature Section of the Document 2 | 3 | Contains: 4 | * Form for 5 | """ 6 | from .TemplateElement import TemplateElement 7 | 8 | 9 | class SectionSignature(TemplateElement): 10 | """ 11 | %% ----------------------------------------------------------------- 12 | %% SECTION - Signature 13 | %% ----------------------------------------------------------------- 14 | \\begin{mdframed}[style=signature] 15 | \\begin{spacing}{1.5} 16 | \\%s{ 17 | Nombre:{\\leaders\\hbox{\\rule{1mm}{0.8pt}}\\hfill} \\\\ 18 | RUT:{\\leaders\\hbox{\\rule{1mm}{0.8pt}}\\hfill} 19 | FECHA:{\\leaders\\hbox{\\rule{1mm}{0.8pt}}\\hfill} \\\\ 20 | Recinto:{\\leaders\\hbox{\\rule{1mm}{0.8pt}}\\hfill} 21 | FIRMA:{\\leaders\\hbox{\\rule{1mm}{0.8pt}}\\hfill} 22 | } 23 | \\end{spacing}%%%% 24 | %%%% 25 | \\tiny{ 26 | El acuse de recibo que se declara en este acto, de acuerdo a lo 27 | dispuesto en la letra b) del Art. 4° y la letra c) del Art. 5° de la 28 | Ley 19.983, acredita que la entrega de mercadería(s) o servicio(s) 29 | prestado(s) ha(n) sido recibido(s). 30 | } 31 | \\end{mdframed} 32 | \\vspace{0.5em} 33 | %s 34 | """ 35 | @property 36 | def carta(self): 37 | return self._template('small') 38 | 39 | @property 40 | def oficio(self): 41 | return self._template('small') 42 | 43 | @property 44 | def thermal80mm(self): 45 | return self._template('scriptsize') 46 | 47 | def _template(self, size): 48 | assert self.__document__, "Have not been yet registered onto a Document" 49 | 50 | if self.__document__.tex_cedible: 51 | if self.__document__.doc_type == 52: 52 | cedible = "CEDIBLE CON SU FACTURA" 53 | else: 54 | cedible = "CEDIBLE" 55 | 56 | return self.__doc__ % (size, "\\raggedleft{\\textbf{%s}}" % cedible) 57 | else: 58 | return "" 59 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionSiiPatch.py: -------------------------------------------------------------------------------- 1 | """ SII Document Patch. 2 | 3 | Contains: 4 | * RUT 5 | * Document Type Name 6 | * Document Serial Number 7 | * SII Branch 8 | 9 | * (thermal*mm only)(optional) Logo (path to EPS) 10 | """ 11 | from .TemplateElement import TemplateElement, Resource 12 | 13 | 14 | DOC_TYPE_STRINGS = { 15 | 33: "FACTURA\\break ELECTRÓNICA", 16 | 34: "FACTURA\\break NO AFECTA O EXENTA\\break ELECTRÓNICA", 17 | 52: "GUÍA DE DESPACHO\\break ELECTRÓNICA", 18 | 56: "NOTA DE DÉBITO\\break ELECTRÓNICA", 19 | 61: "NOTA DE CRÉDITO\\break ELECTRÓNICA", 20 | 46: "FACTURA DE COMPRA\\break ELECTRÓNICA", 21 | 43: "LIQUIDACIÓN FACTURA\\break ELECTRÓNICA", 22 | 110: "FACTURA\\break DE EXPORTACIÓN\\break ELECTRÓNICA", 23 | 111: "NOTA DE DÉBITO\\break DE EXPORTACIÓN\\break ELECTRÓNICA", 24 | 112: "NOTA DE CRÉDITO\\break DE EXPORTACIÓN\\break ELECTRÓNICA" 25 | } 26 | 27 | 28 | class SectionSiiPatch(TemplateElement): 29 | """ 30 | %% ----------------------------------------------------------------- 31 | %% SECTION - Sii Patch 32 | %% ----------------------------------------------------------------- 33 | \\begin{center} 34 | \\begin{mdframed}[style=siipatch] 35 | \\begin{center} 36 | \\large{\\textbf{R.U.T.: %s}}\\break 37 | \\newline 38 | \\large{\\textbf{%s}}\\break 39 | \\newline 40 | \\large{\\textbf{N\\textdegree\\ %s}} 41 | \\end{center} 42 | \\end{mdframed} 43 | \\vspace{0.5em} 44 | \\large{\\textbf{S.I.I. - %s}} 45 | %s 46 | \\end{center} 47 | """ 48 | def __init__(self, rut, dte_type, dte_serial, sii_branch, logo_path=''): 49 | self._rut = rut 50 | self._dte_type = dte_type 51 | self._dte_type_str = DOC_TYPE_STRINGS[dte_type] 52 | self._dte_serial = dte_serial 53 | self._sii_branch = sii_branch 54 | 55 | self._logo_path = logo_path 56 | if self._logo_path: 57 | with open(self._logo_path, 'r') as fh: 58 | self._logo_data = fh.read() 59 | 60 | @property 61 | def resources(self): 62 | ress = [] 63 | 64 | if self._logo_path: 65 | ress.append(Resource('logo.eps', self._logo_data)) 66 | 67 | return ress 68 | 69 | @property 70 | def carta(self): 71 | return self.__doc__ % ( 72 | self._rut, 73 | self._dte_type_str, 74 | self._dte_serial, 75 | self._sii_branch, 76 | '' 77 | ) 78 | 79 | @property 80 | def oficio(self): 81 | return self.__doc__ % ( 82 | self._rut, 83 | self._dte_type_str, 84 | self._dte_serial, 85 | self._sii_branch, 86 | '' 87 | ) 88 | 89 | @property 90 | def thermal80mm(self): 91 | tex_logo = '' 92 | if self._logo_path: 93 | tex_logo += '\\break' 94 | tex_logo += '\\vspace{0.5em}' 95 | tex_logo += '\\includegraphics[width=0.7\\textwidth]{logo.eps}' 96 | 97 | return self.__doc__ % ( 98 | self._rut, 99 | self._dte_type_str, 100 | self._dte_serial, 101 | self._sii_branch, 102 | tex_logo 103 | ) 104 | -------------------------------------------------------------------------------- /src/sii/lib/printing/SectionTotals.py: -------------------------------------------------------------------------------- 1 | """ Totals Section of the Document 2 | 3 | Contains: 4 | * Discount 5 | * Net Amount 6 | * Tax exempt Amount 7 | * Tax 8 | * (optional) Other Tax Types 9 | * Total 10 | """ 11 | from .TemplateElement import TemplateElement 12 | 13 | 14 | SPECIAL_TAX = { 15 | 19: ('IAH', 12, '+'), 16 | 15: ('RTT', 19, '-'), 17 | 33: ('IRM', 8, '-'), 18 | 331: ('IRM', 19, '-'), 19 | 34: ('IRT', 4, '-'), 20 | 39: ('IRPPA', 19, '-') 21 | } 22 | 23 | 24 | class SectionTotals(TemplateElement): 25 | """ 26 | %%%% ----------------------------------------------------------------- 27 | %%%% SECTION - Totals 28 | %%%% ----------------------------------------------------------------- 29 | { 30 | %s 31 | \\begin{longtabu}{%s} 32 | \\rowfont{\\%s} 33 | \\everyrow{\\rowfont{\\%s}} 34 | \\textbf{Desc. Global:} & %s \\$ \\\\ 35 | \\textbf{Monto Neto:} & %s \\$ \\\\ 36 | \\textbf{Monto Exento:} & %s \\$ \\\\ 37 | \\textbf{(19\\%%) IVA:} & %s \\$ \\\\ 38 | %s 39 | 40 | \\tabucline{%s} 41 | 42 | \\textbf{Monto Total:} & %s \\$ \\\\ 43 | \\end{longtabu} 44 | %s 45 | } 46 | """ 47 | def __init__(self, discount, net_value, exempt_value, tax, total, special_tax=None): 48 | """ The only thing to remark here is the **kwargs/"other tax" which 49 | expects the following structure: 50 | 51 | { 52 | 19: (12, 123456), # as in... : (, ) 53 | ... 54 | } 55 | """ 56 | self._payments = [] 57 | 58 | self._discount = discount 59 | self._net_value = net_value 60 | self._exempt_value = exempt_value 61 | self._tax = tax 62 | self._other_tax = special_tax or {} 63 | self._total = total 64 | 65 | @property 66 | def carta(self): 67 | tex = self.__doc__ % ( 68 | '', 69 | 'X[-1r] X[-1r]', 70 | 'small', 71 | 'small', 72 | self._discount, 73 | self._net_value, 74 | self._exempt_value, 75 | self._tax, 76 | self._build_other_tax(), 77 | '2-', 78 | self._total, 79 | '' 80 | ) 81 | return tex 82 | 83 | @property 84 | def oficio(self): 85 | tex = self.__doc__ % ( 86 | '', 87 | 'X[-1r] X[-1r]', 88 | 'small', 89 | 'small', 90 | self._discount, 91 | self._net_value, 92 | self._exempt_value, 93 | self._tax, 94 | self._build_other_tax(), 95 | '2-', 96 | self._total, 97 | '' 98 | ) 99 | return tex 100 | 101 | @property 102 | def thermal80mm(self): 103 | tex_spacing = '\\extrarowsep=^-1pt_-1pt' 104 | tex_spacing += '\\vspace{-1em}' 105 | 106 | tex = self.__doc__ % ( 107 | tex_spacing, 108 | '@{} X[-1l] X[r] @{}', 109 | 'scriptsize', 110 | 'scriptsize', 111 | self._discount, 112 | self._net_value, 113 | self._exempt_value, 114 | self._tax, 115 | self._build_other_tax(), 116 | '-', 117 | self._total, 118 | '\\vspace{-1em}' 119 | ) 120 | return tex 121 | 122 | def _build_other_tax(self): 123 | rows = [] 124 | 125 | for code, detail in self._other_tax.items(): 126 | name, rate, sign = SPECIAL_TAX[code] 127 | _, value = detail 128 | 129 | value_templ = "" 130 | if sign == '-': 131 | value_templ += "-{0}" 132 | else: 133 | value_templ += "{0}" 134 | 135 | rows.append( 136 | '\\textbf{(%s\\%%) %s:} & %s \\$\\\\' % ( 137 | value_templ.format(rate), 138 | name, 139 | value_templ.format(value) 140 | ) 141 | ) 142 | 143 | if rows: 144 | return ('\n' + ' ' * 4 * 3).join(rows) 145 | else: 146 | return '' 147 | -------------------------------------------------------------------------------- /src/sii/lib/printing/TemplateElement.py: -------------------------------------------------------------------------------- 1 | """ Common Interface expected to be implemented by the different TeX Sections (not to be 2 | confused with \section's in actual TeX). 3 | """ 4 | from collections import namedtuple 5 | 6 | Resource = namedtuple( 7 | 'Resource', 8 | [ 9 | 'filename', 10 | 'data' 11 | ] 12 | ) 13 | 14 | 15 | class TemplateElement(object): 16 | 17 | @property 18 | def resources(self): 19 | """ Requires the return of a list of `Resource` object providing 20 | the filename the template is going to expect, and the data that 21 | should be inside of it. 22 | 23 | In case of none, return an empty list. 24 | """ 25 | return [] 26 | 27 | @property 28 | def carta(self) -> str: 29 | """ Create TeX Template for printable medium: "US Letter" 30 | """ 31 | raise NotImplementedError 32 | 33 | @property 34 | def oficio(self) -> str: 35 | """ Create TeX Template for printable medium: "American Foolscap" 36 | """ 37 | raise NotImplementedError 38 | 39 | @property 40 | def thermal80mm(self) -> str: 41 | """ Create TeX Template for printable medium: "Thermal endless 80mm width" 42 | """ 43 | raise NotImplementedError 44 | -------------------------------------------------------------------------------- /src/sii/lib/printing/__init__.py: -------------------------------------------------------------------------------- 1 | """ SII Document Templating and Printing Submodule. 2 | """ 3 | from .Document import Document 4 | 5 | from .SectionPreamble import SectionPreamble 6 | from .SectionEmitter import SectionEmitter, SectionEmitterProvider 7 | from .SectionSiiPatch import SectionSiiPatch 8 | from .SectionReceiver import SectionReceiver 9 | from .SectionItems import SectionItems 10 | from .SectionPayments import SectionPayments 11 | from .SectionTotals import SectionTotals 12 | from .SectionReferences import SectionReferences 13 | from .SectionBarcode import SectionBarcode 14 | from .SectionSignature import SectionSignature 15 | from .SectionDisclaimer import SectionDisclaimer, SectionDisclaimerDummy 16 | 17 | from .printing import list_formats, list_printers 18 | from .printing import create_template, tex_to_pdf 19 | from .printing import print_tex, print_pdf, print_pdf_file 20 | 21 | 22 | __all__ = [ 23 | 'list_formats', 24 | 'list_printers', 25 | 26 | 'create_template', 27 | 'tex_to_pdf', 28 | 29 | 'print_tex', 30 | 'print_pdf', 31 | 'print_pdf_file', 32 | 33 | 'Document', 34 | 35 | 'SectionPreamble', 36 | 'SectionEmitter', 37 | 'SectionEmitterProvider', 38 | 'SectionSiiPatch', 39 | 'SectionReceiver', 40 | 'SectionItems', 41 | 'SectionPayments', 42 | 'SectionTotals', 43 | 'SectionReferences', 44 | 'SectionBarcode', 45 | 'SectionSignature', 46 | 'SectionDisclaimer', 47 | 'SectionDisclaimerDummy' 48 | ] 49 | -------------------------------------------------------------------------------- /src/sii/lib/printing/barcode/Barcode.py: -------------------------------------------------------------------------------- 1 | """ Barcode Creation (PDF417) 2 | """ 3 | import os 4 | 5 | basedir = os.path.split(__file__)[0] 6 | bcdelib = os.path.join(basedir, 'psbcdelib.ps') 7 | 8 | 9 | class Barcode(object): 10 | 11 | __lib__ = open(bcdelib, 'r').read() 12 | 13 | @property 14 | def ps(self): 15 | raise NotImplementedError 16 | 17 | @property 18 | def eps(self): 19 | raise NotImplementedError 20 | -------------------------------------------------------------------------------- /src/sii/lib/printing/barcode/PDF417.py: -------------------------------------------------------------------------------- 1 | """ PDF417 Barcode Generator. 2 | """ 3 | import os 4 | import tempfile 5 | import binascii 6 | 7 | from sii.lib.lib import syscall 8 | 9 | from .Barcode import Barcode 10 | 11 | 12 | class PDF417(Barcode): 13 | """0 0 moveto <{hexdata}> ({options}) /pdf417 /uk.co.terryburton.bwipp findresource exec""" 14 | # % 0 -10 rmoveto (PDF417) show 15 | 16 | def __init__(self, data, rows=None, columns=None): 17 | self.data = data 18 | self.data_hex = binascii.hexlify(data.encode('ISO-8859-1')).decode('utf8') 19 | self.rows = rows 20 | self.columns = columns 21 | 22 | @property 23 | def ps(self): 24 | options = [] 25 | 26 | if self.rows: 27 | options.append('rows={0}'.format(self.rows)) 28 | 29 | if self.columns: 30 | options.append('columns={0}'.format(self.columns)) 31 | 32 | # We append the pdf417 call onto the read in library and return 33 | ps_cmd = '\n\n' 34 | ps_cmd += self.__doc__.format( 35 | hexdata = self.data_hex, 36 | options = ','.join(options) 37 | ) 38 | 39 | return self.__lib__ + ps_cmd 40 | 41 | @property 42 | def eps(self): 43 | return self._eps() 44 | 45 | @property 46 | def eps_filepath(self): 47 | return self._eps(return_path=True) 48 | 49 | def _eps(self, return_path=False): 50 | tmp = tempfile.TemporaryDirectory() 51 | ps_fname = os.path.join(tmp.name, 'barcode.ps') 52 | eps_fname = os.path.join(tmp.name, 'barcode.eps') 53 | result = None 54 | 55 | with open(ps_fname, 'w') as fh: 56 | fh.write(self.ps) 57 | 58 | converter = syscall.Ps2Eps() 59 | converter.call(ps_fname) 60 | 61 | if return_path is True: 62 | return eps_fname 63 | else: 64 | with open(eps_fname, 'rb') as fh: 65 | result = fh.read() 66 | 67 | tmp.cleanup() 68 | return result 69 | -------------------------------------------------------------------------------- /src/sii/lib/printing/barcode/__init__.py: -------------------------------------------------------------------------------- 1 | """ Barcode Generation for the SII Document Printing Submodule. 2 | """ 3 | from .PDF417 import PDF417 4 | 5 | 6 | __all__ = ['PDF417'] 7 | -------------------------------------------------------------------------------- /src/sii/lib/printing/helpers.py: -------------------------------------------------------------------------------- 1 | """ Printing specific helpers. 2 | """ 3 | import re 4 | 5 | __all__ = [ 6 | 'escape_tex' 7 | ] 8 | 9 | 10 | def escape_tex(string): 11 | chars = '%|\$|&' 12 | 13 | return re.sub('(?\s+<', '><', seed_xml) 38 | encoded_xml = clean_xml.encode('utf8') 39 | etree_xml = etree.fromstring(encoded_xml) 40 | return etree_xml 41 | 42 | def _parse_seed_xml(self, seed_etree): 43 | status = seed_etree.xpath('//ESTADO/text()') 44 | seed = seed_etree.xpath('//SEMILLA/text()') 45 | error = seed_etree.xpath('//GLOSA/text()') 46 | 47 | self._status = int(status[0]) if status else None 48 | self._seed = seed[0] if seed else None 49 | self._error = error[0] if error else None 50 | 51 | @property 52 | def status(self): 53 | return self._status 54 | 55 | @property 56 | def seed(self): 57 | # return self._seed 58 | return str(int(self._seed)) 59 | 60 | @property 61 | def error(self): 62 | return self._error 63 | -------------------------------------------------------------------------------- /src/sii/lib/ptcl/Token.py: -------------------------------------------------------------------------------- 1 | """ SII WebService Authentication Token. 2 | """ 3 | import re 4 | 5 | import xmlsec 6 | from suds.client import Client 7 | from lxml import etree 8 | 9 | from .helpers import with_retry 10 | 11 | __all__ = [ 12 | 'Token' 13 | ] 14 | 15 | 16 | class Token(object): 17 | 18 | TOKEN_OK = 0 19 | TOKEN_XML_IO_ERR = 1 20 | TOKEN_XML_SAX_ERR = 2 21 | TOKEN_XML_PARSER_CFG_ERR = 3 22 | TOKEN_XML_SIG_MISSING_ERR = 4 23 | TOKEN_XML_SIG_INVALID_ERR = 5 24 | TOKEN_XML_SEED_MISSING_ERR = 6 25 | TOKEN_MESSAGE_1_ERR = 7 26 | TOKEN_RETURN_ERR = 8 27 | TOKEN_MESSAGE_2_ERR = 9 28 | TOKEN_XML_CERT_MISSING_ERR = 11 29 | TOKEN_PUBKEY_MISMATCH_ERR = 21 30 | TOKEN_AUTH_ERR = -3 31 | TOKEN_USER_ERR = -7 # RUT correct? / User registered with cert auth at SII? 32 | 33 | def __init__(self, seed, key_path, cert_path, sii_host="https://palena.sii.cl/DTEWS/GetTokenFromSeed.jws?wsdl"): 34 | self.seed = seed 35 | self.sii_host = sii_host 36 | self.sii_soap = Client(self.sii_host) 37 | 38 | self.key_path = key_path 39 | self.cert_path = cert_path 40 | 41 | self._status = None 42 | self._token = None 43 | self._error = None 44 | 45 | self._request_etree = self._build_token_request_xml(seed.seed) 46 | self._token_xml = self._fetch_token(self._request_etree) 47 | self._token_etree = self._prepare_token_xml(self._token_xml) 48 | self._token_values = self._parse_token_xml(self._token_etree) 49 | 50 | def _build_token_request_xml(self, seed: str) -> str: 51 | """ 52 | 53 | 54 | 000002360958 55 | 56 | 57 | 58 | """ 59 | # Create the request Frame 60 | root = etree.Element('getToken') 61 | item = etree.SubElement(root, 'item') 62 | seed = etree.SubElement(item, 'Semilla') 63 | seed.text = self.seed.seed 64 | 65 | # Create and insert Signature Template 66 | signode = xmlsec.template.create(root, c14n_method=xmlsec.Transform.C14N, 67 | sign_method=xmlsec.Transform.RSA_SHA1) 68 | 69 | root.append(signode) 70 | 71 | # Add the node to the signature template. 72 | ref = xmlsec.template.add_reference(signode, digest_method=xmlsec.Transform.SHA1) 73 | 74 | # Add the enveloped transform descriptor. 75 | xmlsec.template.add_transform(ref, transform=xmlsec.Transform.ENVELOPED) 76 | 77 | # Add Key Value Info and x509 Data 78 | key_info = xmlsec.template.ensure_key_info(signode) 79 | xmlsec.template.add_key_value(key_info) 80 | xmlsec.template.add_x509_data(key_info) 81 | 82 | # Load Key and Certificate 83 | key = xmlsec.Key.from_file(self.key_path, xmlsec.KeyFormat.PEM) 84 | key.load_cert_from_file(self.cert_path, xmlsec.KeyFormat.PEM) 85 | 86 | # Create Crypto Context and sign Signature Node 87 | ctx = xmlsec.SignatureContext() 88 | ctx.key = key 89 | ctx.sign(signode) 90 | 91 | return root 92 | 93 | def _fetch_token(self, request_etree): 94 | request_xml = etree.tostring( 95 | request_etree, 96 | method='xml', 97 | encoding='unicode' 98 | ) 99 | 100 | return with_retry(lambda: self.sii_soap.service.getToken(request_xml)) 101 | 102 | def _prepare_token_xml(self, token_xml): 103 | clean_xml = re.sub(r'>\s+<', '><', token_xml) 104 | encoded_xml = clean_xml.encode('utf8') 105 | etree_xml = etree.fromstring(encoded_xml) 106 | return etree_xml 107 | 108 | def _parse_token_xml(self, token_etree): 109 | """ 110 | 111 | 112 | 21 113 | Error : Firmar Invalida 114 | 115 | 116 | """ 117 | status = token_etree.xpath('//ESTADO/text()') 118 | token = token_etree.xpath('//TOKEN/text()') 119 | error = token_etree.xpath('//GLOSA/text()') 120 | 121 | self._status = int(status[0]) if status else None 122 | self._token = token[0] if token else None 123 | self._error = error[0] if error else None 124 | 125 | @property 126 | def status(self): 127 | return self._status 128 | 129 | @property 130 | def token(self): 131 | return self._token 132 | 133 | @property 134 | def error(self): 135 | return self._error 136 | -------------------------------------------------------------------------------- /src/sii/lib/ptcl/__init__.py: -------------------------------------------------------------------------------- 1 | """ SII Protocol Objects. 2 | """ 3 | from .Seed import Seed 4 | from .Token import Token 5 | 6 | 7 | __all__ = [ 8 | 'Seed', 9 | 'Token' 10 | ] 11 | 12 | 13 | # Shut up SUDS 14 | import logging 15 | logging.getLogger('suds').setLevel(logging.ERROR) 16 | logging.getLogger('suds.client').setLevel(logging.ERROR) 17 | logging.getLogger('suds.transport').setLevel(logging.ERROR) 18 | logging.getLogger('suds.resolver').setLevel(logging.ERROR) 19 | logging.getLogger('suds.wsdl').setLevel(logging.ERROR) 20 | logging.getLogger('suds.xsd.schema').setLevel(logging.ERROR) 21 | logging.getLogger('suds.xsd.query').setLevel(logging.ERROR) 22 | logging.getLogger('suds.xsd.basic').setLevel(logging.ERROR) 23 | logging.getLogger('suds.binding.marshaller').setLevel(logging.ERROR) 24 | -------------------------------------------------------------------------------- /src/sii/lib/ptcl/helpers.py: -------------------------------------------------------------------------------- 1 | """ Protocol Helpers. 2 | """ 3 | import time 4 | 5 | 6 | __all__ = [ 7 | 'with_retry' 8 | ] 9 | 10 | RETRIES_MAX = 5 11 | RETRIES_SLEEP = 1 12 | 13 | 14 | def with_retry(func, count=RETRIES_MAX, ival=RETRIES_SLEEP): 15 | retries = 0 16 | while retries < count: 17 | try: 18 | return func() 19 | except Exception as exc: 20 | code, msg = exc.args[0] 21 | 22 | if code == 503: 23 | retries += 1 24 | time.sleep(ival) 25 | continue 26 | else: 27 | raise 28 | -------------------------------------------------------------------------------- /src/sii/lib/signature.py: -------------------------------------------------------------------------------- 1 | """ SII Document Signing Process Functions 2 | """ 3 | import xmlsec 4 | 5 | from .helpers import prepend_dtd, extract_signode, extract_signodes 6 | 7 | __all__ = [ 8 | 'sign_document', 9 | 'sign_document_all', 10 | 'build_template' 11 | ] 12 | 13 | 14 | def sign_document(xml, key_path, cert_path): 15 | """ Signs topmost XML node under the document root node. 16 | 17 | :param `etree.Element` xml: The XML to be signed. 18 | 19 | :param str key_path: Path to PEM key file. 20 | :param str cert_path: Path to PEM certificate file. 21 | 22 | :return: `etree.Element` to the signed document. Should be the same with the provided xml param. 23 | """ 24 | # HACK inject a DTD preamble in to direct non-standard xml:id 25 | # resolution for . 26 | xml = prepend_dtd(xml) 27 | 28 | signode = extract_signode(xml) 29 | 30 | # Load Private Key and Public Certificate 31 | key = xmlsec.Key.from_file(key_path, xmlsec.KeyFormat.PEM) 32 | key.load_cert_from_file(cert_path, xmlsec.KeyFormat.PEM) 33 | 34 | # Create Crypto Context and load in Key/Cert 35 | ctx = xmlsec.SignatureContext() 36 | ctx.key = key 37 | 38 | ctx.sign(signode) 39 | 40 | return xml 41 | 42 | 43 | def sign_document_all(xml, key_path, cert_path): 44 | """ Signs all XML's nodes under the document root node. 45 | 46 | :param `etree.Element` xml: The XML to be signed. 47 | 48 | :param str key_path: Path to PEM key file. 49 | :param str cert_path: Path to PEM certificate file. 50 | 51 | :return: `etree.Element` to the signed document. Should be the same with the provided xml param. 52 | 53 | TODO: make sure we get all nodes in depth first order, otherwise we would break envolving 54 | signatures. Its not that it is not currently working, it is just without guaranteed order. 55 | """ 56 | # HACK inject a DTD preamble in to direct non-standard xml:id 57 | # resolution for . 58 | xml = prepend_dtd(xml) 59 | 60 | for signode in extract_signodes(xml): 61 | # Load Private Key and Public Certificate 62 | key = xmlsec.Key.from_file(key_path, xmlsec.KeyFormat.PEM) 63 | key.load_cert_from_file(cert_path, xmlsec.KeyFormat.PEM) 64 | 65 | # Create Crypto Context and load in Key/Cert 66 | ctx = xmlsec.SignatureContext() 67 | ctx.key = key 68 | 69 | ctx.sign(signode) 70 | 71 | return xml 72 | 73 | 74 | def build_template(xml, sig_uri): 75 | """ Build a enveloped template on the provided xml, with a reference 76 | to sig_uri for signature. 77 | 78 | :param etree.Element xml: XML to add the signature node to. 79 | :param str sig_uri: The URI to use for the which points to the node that will digested at 80 | signature-time. 81 | 82 | :return: Returns an `etree.Element` with the template node, ready for signing. 83 | """ 84 | # Create and insert Signature Template 85 | signode = xmlsec.template.create( 86 | xml, 87 | c14n_method=xmlsec.Transform.C14N, 88 | sign_method=xmlsec.Transform.RSA_SHA1 89 | ) 90 | 91 | # Add the node to the signature template. 92 | refnode = xmlsec.template.add_reference( 93 | signode, 94 | digest_method = xmlsec.Transform.SHA1, 95 | uri = sig_uri 96 | ) 97 | 98 | # Add the enveloped transform descriptor. 99 | xmlsec.template.add_transform(refnode, transform=xmlsec.Transform.ENVELOPED) 100 | 101 | # Add Key Value Info and x509 Data 102 | key_info = xmlsec.template.ensure_key_info(signode) 103 | 104 | xmlsec.template.add_key_value(key_info) 105 | xmlsec.template.add_x509_data(key_info) 106 | 107 | return signode 108 | -------------------------------------------------------------------------------- /src/sii/lib/stamping.py: -------------------------------------------------------------------------------- 1 | """ Creation and management of SII Digital Stamp Utilities 2 | """ 3 | import re 4 | import copy 5 | import base64 6 | import datetime as dt 7 | 8 | from Crypto.Signature import PKCS1_v1_5 9 | from Crypto.Hash import SHA as SHA1 10 | from Crypto.PublicKey import RSA 11 | 12 | from .lib import xml 13 | 14 | 15 | def build_digital_stamp(doc_xml, caf_xml): 16 | """ Builds a digital stamp digest from a DTE. 17 | 18 | :param `etree.Element` doc_xml: DTE Document node. 19 | :param `etree.Element` caf_xml: Codigo autorizacion de folios XML. 20 | 21 | :return: `etree.Element` of the 'TED' (Timbre Electronico Digital?) node. 22 | """ 23 | caf = xml.wrap_xml(caf_xml) 24 | doc = xml.wrap_xml(doc_xml) 25 | 26 | stamp = xml.create_xml(name='TED') 27 | doc.TED = stamp 28 | 29 | stamp['version'] = '1.0' 30 | 31 | stamp.DD = xml.create_xml(name='DD') 32 | stamp.DD.RE = doc.Encabezado.Emisor.RUTEmisor._str 33 | stamp.DD.TD = doc.Encabezado.IdDoc.TipoDTE._str 34 | stamp.DD.F = doc.Encabezado.IdDoc.Folio._int 35 | stamp.DD.FE = doc.Encabezado.IdDoc.FchEmis._str 36 | stamp.DD.RR = doc.Encabezado.Receptor.RUTRecep._str 37 | stamp.DD.RSR = doc.Encabezado.Receptor.RznSocRecep._str 38 | stamp.DD.MNT = doc.Encabezado.Totales.MntTotal._int 39 | stamp.DD.IT1 = doc.Detalle.NmbItem._str 40 | stamp.DD.CAF = copy.deepcopy(caf.CAF) 41 | stamp.DD.TSTED = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S') 42 | 43 | digest = xml.create_xml(name='FRMT', value=_build_digital_stamp_digest(doc_xml, caf_xml)) 44 | digest['algoritmo'] = "SHA1withRSA" 45 | stamp.FRMT = digest 46 | 47 | return xml.dump_etree(stamp) 48 | 49 | 50 | def _build_digital_stamp_digest(doc_xml, caf_xml): 51 | doc = xml.wrap_xml(doc_xml) 52 | caf = xml.wrap_xml(caf_xml) 53 | 54 | xml_buff = xml.dump_xml(doc.TED.DD, encoding='ISO-8859-1', xml_declaration=False) 55 | xml_buff = re.sub(b'(?<=>)[\n\r]*(?=<)', b'', xml_buff) 56 | hash = SHA1.new(xml_buff) 57 | 58 | key = RSA.importKey(caf.RSASK._str) 59 | signer = PKCS1_v1_5.new(key) 60 | signature = signer.sign(hash) 61 | 62 | return str(base64.b64encode(signature), 'utf8') 63 | -------------------------------------------------------------------------------- /src/sii/lib/types/Branch.py: -------------------------------------------------------------------------------- 1 | """ Static Company Branch Data (coming from YAML) 2 | 3 | For a Template example look into files/companies.yml at the root of the repository. 4 | """ 5 | 6 | 7 | class Branch(object): 8 | 9 | def __init__(self, yml): 10 | self.__dict__.update(yml) 11 | 12 | def __getattr__(self, key): 13 | if key.startswith('__'): 14 | return super().__getattr__(key) 15 | 16 | if key in self.__dict__: 17 | return super().__getattr__(key) 18 | else: 19 | raise RuntimeError("Expected and did not find <{0}> in branch section of YAML.".format(key)) 20 | -------------------------------------------------------------------------------- /src/sii/lib/types/CAF.py: -------------------------------------------------------------------------------- 1 | """ Wrapper for CAF XML's as provided by the SII at Document ID Signature Keypair Request 2 | """ 3 | from lxml import etree 4 | 5 | 6 | class CAF(object): 7 | 8 | def __init__(self, caf_xml): 9 | if isinstance(caf_xml, str): 10 | self._root = etree.fromstring(caf_xml) 11 | elif isinstance(caf_xml, etree._Element): 12 | self._root = caf_xml 13 | else: 14 | raise ValueError("Expected XML string or etree.Element as argument") 15 | 16 | def __str__(self): 17 | return etree.tostring(self._root, encoding='unicode') 18 | 19 | @property 20 | def xml(self): 21 | return self._root 22 | 23 | @property 24 | def company_rut(self): 25 | rut = self._ctrld_xpath( 26 | '//RE/text()', 27 | "Could not parse company RUT in CAF:\n{0}".format(str(self)) 28 | ) 29 | return rut 30 | 31 | @property 32 | def dte_type(self): 33 | typ = self._ctrld_xpath( 34 | '//TD/text()', 35 | "Could not parse document type in CAF:\n{0}".format(str(self)) 36 | ) 37 | return int(typ) 38 | 39 | @property 40 | def dte_id_from(self): 41 | id_from = self._ctrld_xpath( 42 | '//RNG/D/text()', 43 | "Could not parse range in CAF:\n{0}".format(str(self)) 44 | ) 45 | return int(id_from) 46 | 47 | @property 48 | def dte_id_until(self): 49 | id_until = self._ctrld_xpath( 50 | '//RNG/H/text()', 51 | "Could not parse range in CAF:\n{0}".format(str(self)) 52 | ) 53 | return int(id_until) 54 | 55 | @property 56 | def private_key(self): 57 | return self._ctrld_xpath( 58 | '//RSASK/text()', 59 | "Could not parse private key in CAF:\n{0}".format(str(self)) 60 | ) 61 | 62 | @property 63 | def public_key(self): 64 | return self._ctrld_xpath( 65 | '//RSAPUBK/text()', 66 | "Could not parse public key in CAF:\n{0}".format(str(self)) 67 | ) 68 | 69 | def _ctrld_xpath(self, xpath, failmsg): 70 | values = self._root.xpath(xpath) 71 | 72 | if not values: 73 | raise RuntimeError(failmsg) 74 | elif len(values) > 1: 75 | raise RuntimeError("Found more than one values matching " 76 | "\"{0}\" in:\n{1}".format(xpath, self.xml)) 77 | else: 78 | return values[0] 79 | -------------------------------------------------------------------------------- /src/sii/lib/types/CAFPool.py: -------------------------------------------------------------------------------- 1 | """ Wrapper around CAF File. 2 | 3 | This is currently only a proxy to an internal object from cns.lib.sii. 4 | """ 5 | import os 6 | import collections 7 | 8 | from lxml import etree 9 | 10 | from ..lib import xml 11 | 12 | from .CAF import CAF 13 | 14 | __all__ = [ 15 | "CAFPool" 16 | ] 17 | 18 | read_rut = lambda raw: int(raw.split('-')[0]) 19 | 20 | 21 | class CAFPool(object): 22 | 23 | def __init__(self, dirpath): 24 | self._path = os.path.abspath(os.path.expanduser(dirpath)) 25 | self._fnames = [fname for fname in os.listdir(self._path) if os.path.splitext(fname)[-1] == ".xml"] 26 | self._fpaths = [os.path.join(self._path, fname) for fname in self._fnames] 27 | self._trees = [_read_caf(fname) for fname in self._fpaths] 28 | self._cafs = [CAF(tree) for tree in self._trees] 29 | 30 | self._idx_company = collections.defaultdict(lambda: list()) 31 | for caf in self._cafs: 32 | rut = read_rut(caf.company_rut) 33 | self._idx_company[rut].append(caf) 34 | 35 | def resolve(self, rut, dte_type, dte_id): 36 | """ Returns CAF if available for given information. """ 37 | cafs = self._idx_company[rut] 38 | typed = [caf for caf in cafs if caf.dte_type == dte_type] 39 | 40 | for caf in typed: 41 | if caf.dte_id_from <= dte_id <= caf.dte_id_until: 42 | return caf 43 | 44 | raise RuntimeError( 45 | "Could not find CAF for document ".format(rut, dte_type, dte_id) 46 | ) 47 | 48 | def resolve_document(self, dte): 49 | """ Returns CAF if available for given DTE inner . """ 50 | dte = xml.wrap_xml(dte) 51 | 52 | dte_type = int(dte.Encabezado.IdDoc.TipoDTE) 53 | dte_id = int(dte.Encabezado.IdDoc.Folio) 54 | rut_full = str(dte.Encabezado.Emisor.RUTEmisor) 55 | 56 | rut = read_rut(rut_full) 57 | 58 | return self.resolve(rut, dte_type, dte_id) 59 | 60 | 61 | def _read_caf(path): 62 | with open(path, "rb") as fh: 63 | xml = etree.parse(fh) 64 | 65 | return xml.getroot() 66 | -------------------------------------------------------------------------------- /src/sii/lib/types/Company.py: -------------------------------------------------------------------------------- 1 | """ Static Company Data (coming from YAML) 2 | 3 | For a Template example look into files/companies.yml at the root of the repository. 4 | """ 5 | from .Branch import Branch 6 | 7 | 8 | class Company(object): 9 | 10 | def __init__(self, data): 11 | self.__dict__.update(data) 12 | self.branches = [Branch(b) for b in self.branches] 13 | 14 | def __getattr__(self, key): 15 | if key.startswith('__'): 16 | return super().__getattr__(key) 17 | 18 | if key in self.__dict__: 19 | return super().__getattr__(key) 20 | else: 21 | raise RuntimeError("Expected and did not find <{0}> in company YAML.".format(key)) 22 | -------------------------------------------------------------------------------- /src/sii/lib/types/CompanyPool.py: -------------------------------------------------------------------------------- 1 | """ Company Pool to resolve Company objects from (coming from YAML) 2 | 3 | For a Template example look into files/companies.yml at the root of the repository. Such a file can 4 | hold more than one instances of company metadata inside, allowing for multiple companies being 5 | handled transparently by the same library or client/server application. 6 | """ 7 | import io 8 | 9 | import yaml 10 | 11 | from sii.lib.lib import fileio 12 | 13 | from .Company import Company 14 | from .Branch import Branch 15 | 16 | 17 | class CompanyPool(object): 18 | 19 | def __init__(self, yml): 20 | self._companies = {} 21 | 22 | for rut, data in yml.items(): 23 | self._companies[rut] = Company(data) 24 | 25 | def __getitem__(self, key): 26 | company = self._companies.get(key, None) 27 | 28 | if company is None: 29 | raise KeyError("Expected and did not find company with RUT: <{0}> in YAML.".format(key)) 30 | 31 | return company 32 | 33 | @classmethod 34 | def from_file(cls, path): 35 | buff = io.StringIO(fileio.read(path)) 36 | yml = yaml.load(buff) 37 | 38 | return cls(yml) 39 | -------------------------------------------------------------------------------- /src/sii/lib/types/__init__.py: -------------------------------------------------------------------------------- 1 | """ All SII Types and convencience Wrappers that cannot be directly put in the Schema or 2 | Communication Protocols. 3 | """ 4 | from .CAF import CAF 5 | from .CAFPool import CAFPool 6 | 7 | from .Company import Company 8 | from .CompanyPool import CompanyPool 9 | from .Branch import Branch 10 | 11 | 12 | __all__ = [ 13 | 'CAF', 14 | 'CAFPool', 15 | 'Company', 16 | 'CompanyPool', 17 | 'Branch' 18 | ] 19 | -------------------------------------------------------------------------------- /src/sii/lib/upload.py: -------------------------------------------------------------------------------- 1 | """ SII Documents Upload Utilities 2 | """ 3 | import requests 4 | from lxml import etree 5 | 6 | from . import lib 7 | from . import ptcl 8 | 9 | from .validation import validate_schema, validate_signatures 10 | 11 | xml = lib.xml 12 | 13 | __all__ = [ 14 | 'test_connection', 15 | 'upload_document' 16 | ] 17 | 18 | HOST_TESTING = 'https://maullin.sii.cl' 19 | HOST_PRODUCTION = 'https://palena.sii.cl' 20 | 21 | STATUS_DESC = { 22 | "0" : None, 23 | "1" : "El Sender no tiene permiso para enviar", 24 | "2" : "Error en tamaño del archivo (muy grande o muy chico)", 25 | "3" : "Archivo cortado (tamaño <> al parámetro size)", 26 | "5" : "No está autenticado", 27 | "6" : "Empresa no autorizada a enviar archivos", 28 | "7" : "Esquema Invalido", 29 | "8" : "Firma del Documento", 30 | "9" : "Sistema Bloqueado", 31 | "99" : "Error Interno." 32 | } 33 | 34 | 35 | def test_connection(key_pth, cert_pth, server): 36 | try: 37 | token = connect_webservice(key_pth, cert_pth, server) 38 | except Exception as exc: 39 | return str(exc) 40 | 41 | if token.status == 0: 42 | return True 43 | else: 44 | return token.status 45 | 46 | 47 | def upload_document(document, key_pth, cert_pth, server=HOST_PRODUCTION, dryrun=False, verify=True): 48 | """ Upload a ready and signed . 49 | """ 50 | # Verify Signature and Schema 51 | validate_signatures(document) 52 | validate_schema(document) 53 | 54 | # Prepare payload 55 | xmlbuff = etree.tostring( 56 | document, 57 | pretty_print = True, 58 | method = 'xml', 59 | encoding = 'ISO-8859-1', 60 | xml_declaration = False 61 | ) 62 | xmlbuff = b'\n' + xmlbuff 63 | 64 | envio = xml.wrap_xml(document) 65 | if envio.__name__ == '{http://www.sii.cl/SiiDte}EnvioDTE': 66 | rut_company, dv_company = envio.SetDTE.Caratula.RutEmisor._str.split('-') 67 | rut_sender, dv_sender = envio.SetDTE.Caratula.RutEnvia._str.split('-') 68 | elif envio.__name__ == '{http://www.sii.cl/SiiDte}LibroCompraVenta': 69 | rut_company, dv_company = envio.EnvioLibro.Caratula.RutEmisorLibro._str.split('-') 70 | rut_sender, dv_sender = envio.EnvioLibro.Caratula.RutEnvia._str.split('-') 71 | elif envio.__name__ == '{http://www.sii.cl/SiiDte}LibroGuia': 72 | rut_company, dv_company = envio.EnvioLibro.Caratula.RutEmisorLibro._str.split('-') 73 | rut_sender, dv_sender = envio.EnvioLibro.Caratula.RutEnvia._str.split('-') 74 | else: 75 | raise TypeError( 76 | "Document upload for '{0}' not available or not yet implemented" 77 | .format(envio.__name__) 78 | ) 79 | 80 | # Create HTML Request and Upload 81 | req = requests.Request() 82 | 83 | req.method = 'POST' 84 | req.url = server + '/cgi_dte/UPL/DTEUpload' 85 | req.encoding = 'ISO-8859-1' 86 | 87 | req.headers['Referer'] = "http://www.voipir.cl" 88 | req.headers['User-Agent'] = "Mozilla/4.0 (compatible; PROG 1.0; Windows NT 5.0; YComp 5.0.2.4)" 89 | 90 | req.files.extend([ 91 | ('rutSender', ('', rut_sender)), 92 | ('dvSender', ('', dv_sender)), 93 | ('rutCompany', ('', rut_company)), 94 | ('dvCompany', ('', dv_company)), 95 | ('file', ('upload.xml', xmlbuff, 'text/xml; charset=ISO-8859-1')) 96 | ]) 97 | 98 | # Get Session ptcl.Token 99 | token = connect_webservice(key_pth, cert_pth, server) 100 | if not token.status == 0: 101 | raise RuntimeError("Could not connect to {0}".format(server)) 102 | req.headers['Cookie'] = "TOKEN={0}".format(token.token) 103 | 104 | # Get and interpret (TODO) response 105 | prepared = req.prepare() 106 | 107 | if dryrun: 108 | dummy = """ 109 | 110 | 9-9 111 | 9-9 112 | somebullshit 113 | 2016-09-09 09:09:09 114 | 0 115 | 999 116 | 117 | """ 118 | 119 | dummy = etree.fromstring(dummy) 120 | return _parse_upload_return(dummy) 121 | else: 122 | sess = requests.Session() 123 | resp = sess.send(prepared, verify=verify) 124 | 125 | resp_xml = etree.fromstring(resp.text) 126 | return _parse_upload_return(resp_xml) 127 | 128 | 129 | def connect_webservice(key_pth, cert_pth, server): 130 | ws_url_seed = server + '/DTEWS/CrSeed.jws?wsdl' 131 | ws_url_token = server + '/DTEWS/GetTokenFromSeed.jws?wsdl' 132 | 133 | auth_seed = ptcl.Seed(sii_host=ws_url_seed) 134 | auth_token = ptcl.Token(auth_seed, key_pth, cert_pth, sii_host=ws_url_token) 135 | 136 | return auth_token 137 | 138 | 139 | def _parse_upload_return(tree): 140 | ret = xml.wrap_xml(tree) 141 | 142 | status = ret.STATUS._str 143 | if status == "0": 144 | return ret.TRACKID._int 145 | else: 146 | raise RuntimeError("SII: '{0}'".format(STATUS_DESC[status])) 147 | -------------------------------------------------------------------------------- /src/sii/lib/validation.py: -------------------------------------------------------------------------------- 1 | """ SII Document Signature Verification Process Functions 2 | """ 3 | from io import BytesIO 4 | from copy import deepcopy 5 | import tempfile 6 | 7 | import xmlsec 8 | from lxml import etree 9 | 10 | from .schemas import resolve_schema 11 | from .helpers import ( 12 | prepend_dtd, 13 | extract_signodes, 14 | extract_signode_certificate, 15 | extract_signode_reference, 16 | ) 17 | 18 | __all__ = [ 19 | 'validate_signatures', 20 | 'validate_schema' 21 | ] 22 | 23 | 24 | def validate_signatures(xml): 25 | """ Validate internal Document Signatures. Public Key are provided by them, so no need for 26 | anything else than the XML itself. 27 | 28 | :param `etree.Element` xml: Element to the rootnode of the document. 29 | 30 | :return: [tuple(URI, True | False), ...] 31 | """ 32 | xml = prepend_dtd(xml) 33 | 34 | signodes = extract_signodes(xml) 35 | results = [] 36 | for signode in signodes: 37 | cert = extract_signode_certificate(signode) 38 | ref = extract_signode_reference(signode) 39 | 40 | # Load Public Key 41 | key_mgr = xmlsec.KeysManager() 42 | with tempfile.NamedTemporaryFile(mode='wb', buffering=0) as tmpf: 43 | tmpf.write(bytes(cert, 'utf8')) 44 | 45 | key_mgr.load_cert(tmpf.name, xmlsec.KeyFormat.PEM, xmlsec.KeyDataType.TRUSTED) 46 | 47 | # Verify the Document 48 | ctx = xmlsec.SignatureContext(key_mgr) 49 | 50 | try: 51 | ctx.verify(signode) 52 | except xmlsec.error.Error: 53 | validity = (ref.attrib['URI'], False) 54 | else: 55 | validity = (ref.attrib['URI'], True) 56 | 57 | results.append(validity) 58 | 59 | return results 60 | 61 | 62 | def validate_schema(doc_xml, schema_xml=None): 63 | """ Validate XML against its XSD Schema definition provided by the SII. 64 | 65 | :param `lxml.etree.Element` doc_xml: Handle to XML etree root node. 66 | """ 67 | doc_xml = deepcopy(doc_xml) 68 | 69 | doc_new = etree.Element(doc_xml.tag, nsmap={None: 'http://www.sii.cl/SiiDte'}) 70 | doc_new[:] = doc_xml[:] # move children into new root 71 | doc_new.attrib.update(doc_xml.attrib) # copy attributes of the root node 72 | 73 | # reload xml 74 | buff = BytesIO(etree.tostring(doc_new, method='c14n')) 75 | xml = etree.parse(buff).getroot() 76 | 77 | if not schema_xml: 78 | schema_pth = resolve_schema(doc_xml) 79 | 80 | with open(schema_pth, 'rb') as fh: 81 | schema_xml = etree.parse(fh) 82 | 83 | schema = etree.XMLSchema(schema_xml) 84 | schema.assertValid(xml) 85 | 86 | return True # if no assertion gets thrown above, we can safely assume a `True` validity. 87 | --------------------------------------------------------------------------------