├── .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 |
--------------------------------------------------------------------------------