Skip to content

Commit

Permalink
Merge pull request #46 from fyndata/develop
Browse files Browse the repository at this point in the history
Release 0.6.2
  • Loading branch information
glarrain committed May 15, 2019
2 parents 31c1972 + 0b6ee85 commit dba26db
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.6.1
current_version = 0.6.2
commit = True
tag = True

Expand Down
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
History
-------

0.6.2 (2019-05-15)
+++++++++++++++++++++++

* (PR #45, 2019-05-15) libs.encoding_utils: improve ``clean_base64``
* (PR #44, 2019-05-15) dte.parse: fix edge case in ``parse_dte_xml``

0.6.1 (2019-05-08)
+++++++++++++++++++++++

Expand Down
2 changes: 1 addition & 1 deletion cl_sii/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
"""


__version__ = '0.6.1'
__version__ = '0.6.2'
70 changes: 54 additions & 16 deletions cl_sii/dte/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import logging
import os
from datetime import date, datetime
from typing import Tuple
from typing import Optional, Tuple

from cl_sii.libs import encoding_utils
from cl_sii.libs import tz_utils
Expand Down Expand Up @@ -430,32 +430,37 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteDataL2:
# values parsing
###########################################################################

tipo_dte_value = constants.TipoDteEnum(int(tipo_dte_em.text.strip()))
folio_value = int(folio_em.text.strip())
fecha_emision_value = date.fromisoformat(fecha_emision_em.text.strip())
tipo_dte_value = constants.TipoDteEnum(int(_text_strip_or_raise(tipo_dte_em)))
folio_value = int(_text_strip_or_raise(folio_em))
fecha_emision_value = date.fromisoformat(_text_strip_or_raise(fecha_emision_em))
fecha_vencimiento_value = None
if fecha_vencimiento_em is not None:
fecha_vencimiento_value = date.fromisoformat(fecha_vencimiento_em.text.strip())
fecha_vencimiento_value = date.fromisoformat(
_text_strip_or_raise(fecha_vencimiento_em))

emisor_rut_value = Rut(emisor_rut_em.text.strip())
emisor_razon_social_value = emisor_razon_social_em.text.strip()
emisor_giro_value = emisor_giro_em.text.strip()
emisor_email_value = emisor_email_em.text.strip() if emisor_email_em is not None else None
emisor_rut_value = Rut(_text_strip_or_raise(emisor_rut_em))
emisor_razon_social_value = _text_strip_or_raise(emisor_razon_social_em)
emisor_giro_value = _text_strip_or_raise(emisor_giro_em)
emisor_email_value = None
if emisor_email_em is not None:
emisor_email_value = _text_strip_or_none(emisor_email_em)

receptor_rut_value = Rut(receptor_rut_em.text.strip())
receptor_razon_social_value = receptor_razon_social_em.text.strip()
receptor_email_value = receptor_email_em.text.strip() if receptor_email_em is not None else None
receptor_rut_value = Rut(_text_strip_or_raise(receptor_rut_em))
receptor_razon_social_value = _text_strip_or_raise(receptor_razon_social_em)
receptor_email_value = None
if receptor_email_em is not None:
receptor_email_value = _text_strip_or_none(receptor_email_em)

monto_total_value = int(monto_total_em.text.strip())
monto_total_value = int(_text_strip_or_raise(monto_total_em))

tmst_firma_value = tz_utils.convert_naive_dt_to_tz_aware(
dt=datetime.fromisoformat(tmst_firma_em.text),
dt=datetime.fromisoformat(_text_strip_or_raise(tmst_firma_em)),
tz=data_models.DteDataL2.DATETIME_FIELDS_TZ)

signature_signature_value = encoding_utils.decode_base64_strict(
signature_signature_value_em.text.strip())
_text_strip_or_raise(signature_signature_value_em))
signature_key_info_x509_cert_der = encoding_utils.decode_base64_strict(
signature_key_info_x509_cert_em.text.strip())
_text_strip_or_raise(signature_key_info_x509_cert_em))

return data_models.DteDataL2(
emisor_rut=emisor_rut_value,
Expand All @@ -476,6 +481,39 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteDataL2:
)


def _text_strip_or_none(xml_em: XmlElement) -> Optional[str]:
# note: we need the pair of functions '_text_strip_or_none' and '_text_strip_or_raise'
# because, under certain circumstances, an XML tag:
# - with no content -> `xml_em.text` is None instead of ''
# - with leading and/or trailing whitespace -> `xml_em.text` may or may not include that

if xml_em is None:
raise ValueError("Value must be an XML element, not None.")

stripped_text: Optional[str] = None
if xml_em.text is not None:
stripped_text = xml_em.text.strip()

return stripped_text


def _text_strip_or_raise(xml_em: XmlElement) -> str:
# note: we need the pair of functions '_text_strip_or_none' and '_text_strip_or_raise'
# because, under certain circumstances, an XML tag:
# - with no content -> `xml_em.text` is None instead of ''
# - with leading and/or trailing whitespace -> `xml_em.text` may or may not include that

if xml_em is None:
raise ValueError("Value must be an XML element, not None.")

if xml_em.text is None:
raise ValueError("Text of XML element is None.")
else:
stripped_text: str = xml_em.text.strip()

return stripped_text


###############################################################################
# helpers
###############################################################################
Expand Down
8 changes: 7 additions & 1 deletion cl_sii/libs/encoding_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ def clean_base64(value: Union[str, bytes]) -> bytes:
raise TypeError("Value must be str or bytes.")

# remove line breaks and spaces
value_base64_bytes_cleaned = value_base64_bytes.replace(b'\n', b'').replace(b' ', b'')
# warning: we may only remove characters that are not part of the standard base-64 alphabet
# (or any of its popular alternatives).
value_base64_bytes_cleaned = value_base64_bytes \
.replace(b'\n', b'') \
.replace(b'\r', b'') \
.replace(b'\t', b'') \
.replace(b' ', b'')

return value_base64_bytes_cleaned

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?xml version='1.0' encoding='ISO-8859-1'?>
<DTE xmlns="http://www.sii.cl/SiiDte" version="1.0">
<!-- O Win32 Chrome 73 central VERSION: v20190227 -->
<Documento ID="MiPE76354771-13419">
<Encabezado>
<IdDoc>
<TipoDTE>33</TipoDTE>
<Folio>170</Folio>
<FchEmis>2019-04-01</FchEmis>
<TpoTranCompra>1</TpoTranCompra>
<TpoTranVenta>1</TpoTranVenta>
<FmaPago>2</FmaPago>
</IdDoc>
<Emisor>
<RUTEmisor>76354771-K</RUTEmisor>
<RznSoc>INGENIERIA ENACON SPA</RznSoc>
<GiroEmis>Ingenieria y Construccion</GiroEmis>
<CorreoEmisor></CorreoEmisor>
<Acteco>421000</Acteco>
<CdgSIISucur>078525666</CdgSIISucur>
<DirOrigen>MERCED 753 16 ARBOLEDA DE QUIILOTA</DirOrigen>
<CmnaOrigen>QUILLOTA</CmnaOrigen>
<CiudadOrigen>QUILLOTA</CiudadOrigen>
</Emisor>
<Receptor>
<RUTRecep>96790240-3</RUTRecep>
<RznSocRecep>MINERA LOS PELAMBRES</RznSocRecep>
<GiroRecep>EXTRACCION Y PROCESAMIENTO DE COBRE</GiroRecep>
<CorreoRecep></CorreoRecep>
<Contacto>Felipe Barria</Contacto>
<DirRecep>Av. Apoquindo 4001 1802</DirRecep>
<CmnaRecep>LAS CONDES</CmnaRecep>
<CiudadRecep>SANTIAGO</CiudadRecep>
</Receptor>
<Totales>
<MntNeto>2517900</MntNeto>
<TasaIVA>19.00</TasaIVA>
<IVA>478401</IVA>
<MntTotal>2996301</MntTotal>
</Totales>
</Encabezado>
<Detalle>
<NroLinDet>1</NroLinDet>
<NmbItem>Tableros electricos 3 tom</NmbItem>
<DscItem>as 3p + t; 380v; 50 hz; 32a; 3 tomas monofasicas 2p + t; 240v; 50 hz; 16a; proteccion ip, segun orden de compra de la referencia.-</DscItem>
<QtyItem>2.00</QtyItem>
<UnmdItem>Unid</UnmdItem>
<PrcItem>1258950.00</PrcItem>
<MontoItem>2517900</MontoItem>
</Detalle>
<Referencia>
<NroLinRef>1</NroLinRef>
<TpoDocRef>801</TpoDocRef>
<FolioRef>4510083633</FolioRef>
<FchRef>2019-03-22</FchRef>
</Referencia>
<TED version="1.0"><DD><RE>76354771-K</RE><TD>33</TD><F>170</F><FE>2019-04-01</FE><RR>96790240-3</RR><RSR>MINERA LOS PELAMBRES</RSR><MNT>2996301</MNT><IT1>Tableros electricos 3 tom</IT1><CAF version="1.0"><DA><RE>76354771-K</RE><RS>INGENIERIA ENACON SPA</RS><TD>33</TD><RNG><D>170</D><H>170</H></RNG><FA>2019-04-01</FA><RSAPK><M>uv7BUO3yg/7RoMjh1mPXXG/8YIwjtXsu7kcOq7dZQj66QCiY4FVz2fIhF1jaU0GSikq/jq26IFGylGus92OnPQ==</M><E>Aw==</E></RSAPK><IDK>300</IDK></DA><FRMA algoritmo="SHA1withRSA">PI7bw8y0RNUJrGxyhb2gr6BjFtv/Ikyo/6g69wycoXTHSoRML3xvZvOBytreN7REw9JF0Ldoj91RRtaZbH38bA==</FRMA></CAF><TSTED>2019-04-01T01:36:40</TSTED></DD><FRMT algoritmo="SHA1withRSA">DKFS7bNYRpVYLNEII+eyLcBHmNwQIHVkbqgR96wKcnDEcU6NsHQUMUyXpr7ql7xD9iuGkZDmNxHuY+Mq913oSA==</FRMT></TED>
<TmstFirma>2019-04-01T01:36:40</TmstFirma>

</Documento>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#MiPE76354771-13419">
<Transforms>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>ij2Qn6xOc2eRx3hwyO/GrzptoBk=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
fsYP5p/lNfofAz8POShrJjqXdBTNNtvv4/TWCxbvwTIAXr7BLrlvX3C/Hpfo4viqaxSu1OGFgPnk
ddDIFwj/ZsVdbdB+MhpKkyha83RxhJpYBVBY3c+y9J6oMfdIdMAYXhEkFw8w63KHyhdf2E9dnbKi
wqSxDcYjTT6vXsLPrZk=
</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>
pB4Bs0Op+L0za/zpFQYBiCrVlIOKgULo4uvRLCI5picuxI6X4rE7f3g9XBIZrqtmTUSshmifKLXl
9T/ScdkuLyIcsHj0QHkbe0LCHSzw1+pH1yTT/dn5NeFVR2InIkL/PzHkjmVJR/M0R50lGJ1W+nqN
Uavs/9J+gR9BBMs/eYE=
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
<X509Data>
<X509Certificate>
MIIGVDCCBTygAwIBAgIKMUWmvgAAAAjUHTANBgkqhkiG9w0BAQUFADCB0jELMAkGA1UEBhMCQ0wx
HTAbBgNVBAgTFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQHEwhTYW50aWFnbzEUMBIGA1UE
ChMLRS1DRVJUQ0hJTEUxIDAeBgNVBAsTF0F1dG9yaWRhZCBDZXJ0aWZpY2Fkb3JhMTAwLgYDVQQD
EydFLUNFUlRDSElMRSBDQSBGSVJNQSBFTEVDVFJPTklDQSBTSU1QTEUxJzAlBgkqhkiG9w0BCQEW
GHNjbGllbnRlc0BlLWNlcnRjaGlsZS5jbDAeFw0xNzA5MDQyMTExMTJaFw0yMDA5MDMyMTExMTJa
MIHXMQswCQYDVQQGEwJDTDEUMBIGA1UECBMLVkFMUEFSQUlTTyAxETAPBgNVBAcTCFF1aWxsb3Rh
MS8wLQYDVQQKEyZTZXJ2aWNpb3MgQm9uaWxsYSB5IExvcGV6IHkgQ2lhLiBMdGRhLjEkMCIGA1UE
CwwbSW5nZW5pZXLDrWEgeSBDb25zdHJ1Y2Npw7NuMSMwIQYDVQQDExpSYW1vbiBodW1iZXJ0byBM
b3BleiAgSmFyYTEjMCEGCSqGSIb3DQEJARYUZW5hY29ubHRkYUBnbWFpbC5jb20wgZ8wDQYJKoZI
hvcNAQEBBQADgY0AMIGJAoGBAKQeAbNDqfi9M2v86RUGAYgq1ZSDioFC6OLr0SwiOaYnLsSOl+Kx
O394PVwSGa6rZk1ErIZonyi15fU/0nHZLi8iHLB49EB5G3tCwh0s8NfqR9ck0/3Z+TXhVUdiJyJC
/z8x5I5lSUfzNEedJRidVvp6jVGr7P/SfoEfQQTLP3mBAgMBAAGjggKnMIICozA9BgkrBgEEAYI3
FQcEMDAuBiYrBgEEAYI3FQiC3IMvhZOMZoXVnReC4twnge/sPGGBy54UhqiCWAIBZAIBBDAdBgNV
HQ4EFgQU1dVHhF0UVe7RXIz4cjl3/Vew+qowCwYDVR0PBAQDAgTwMB8GA1UdIwQYMBaAFHjhPp/S
ErN6PI3NMA5Ts0MpB7NVMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9jcmwuZS1jZXJ0Y2hpbGUu
Y2wvZWNlcnRjaGlsZWNhRkVTLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0dHA6
Ly9vY3NwLmVjZXJ0Y2hpbGUuY2wvb2NzcDAjBgNVHREEHDAaoBgGCCsGAQQBwQEBoAwWCjEzMTg1
MDk1LTYwIwYDVR0SBBwwGqAYBggrBgEEAcEBAqAMFgo5NjkyODE4MC01MIIBTQYDVR0gBIIBRDCC
AUAwggE8BggrBgEEAcNSBTCCAS4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cuZS1jZXJ0Y2hpbGUu
Y2wvQ1BTLmh0bTCB/AYIKwYBBQUHAgIwge8egewAQwBlAHIAdABpAGYAaQBjAGEAZABvACAARgBp
AHIAbQBhACAAUwBpAG0AcABsAGUALgAgAEgAYQAgAHMAaQBkAG8AIAB2AGEAbABpAGQAYQBkAG8A
IABlAG4AIABmAG8AcgBtAGEAIABwAHIAZQBzAGUAbgBjAGkAYQBsACwAIABxAHUAZQBkAGEAbgBk
AG8AIABoAGEAYgBpAGwAaQB0AGEAZABvACAAZQBsACAAQwBlAHIAdABpAGYAaQBjAGEAZABvACAA
cABhAHIAYQAgAHUAcwBvACAAdAByAGkAYgB1AHQAYQByAGkAbzANBgkqhkiG9w0BAQUFAAOCAQEA
mxtPpXWslwI0+uJbyuS9s/S3/Vs0imn758xMU8t4BHUd+OlMdNAMQI1G2+q/OugdLQ/a9Sg3clKD
qXR4lHGl8d/Yq4yoJzDD3Ceez8qenY3JwGUhPzw9oDpg4mXWvxQDXSFeW/u/BgdadhfGnpwx61Un
+/fU24ZgU1dDJ4GKj5oIPHUIjmoSBhnstEhIr6GJWSTcDKTyzRdqBlaVhenH2Qs6Mw6FrOvRPuud
B7lo1+OgxMb/Gjyu6XnEaPu7Vq4XlLYMoCD2xrV7WEADaDTm7KcNLczVAYqWSF1WUqYSxmPoQDFY
+kMTThJyCXBlE0NADInrkwWgLLygkKI7zXkwaw==
</X509Certificate>
</X509Data>
</KeyInfo>
</Signature></DTE>
28 changes: 28 additions & 0 deletions tests/test_dte_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ def setUpClass(cls) -> None:
'test_data/sii-dte/DTE--76354771-K--33--170--cleaned.xml')
cls.dte_clean_xml_2_xml_bytes = read_test_file_bytes(
'test_data/sii-dte/DTE--76399752-9--33--25568--cleaned.xml')
cls.dte_clean_xml_1b_xml_bytes = read_test_file_bytes(
'test_data/sii-dte/DTE--76354771-K--33--170--cleaned-mod-empty-emails.xml')

cls.dte_clean_xml_1_cert_pem_bytes = encoding_utils.clean_base64(
crypto_utils.remove_pem_cert_header_footer(
Expand Down Expand Up @@ -364,6 +366,32 @@ def test_parse_dte_xml_ok_1(self) -> None:
receptor_email=None,
))

def test_parse_dte_xml_ok_1b(self) -> None:
xml_doc = xml_utils.parse_untrusted_xml(self.dte_clean_xml_1b_xml_bytes)

parsed_dte = parse_dte_xml(xml_doc)
self.assertDictEqual(
dict(parsed_dte.as_dict()),
dict(
emisor_rut=Rut('76354771-K'),
tipo_dte=cl_sii.dte.constants.TipoDteEnum.FACTURA_ELECTRONICA,
folio=170,
fecha_emision_date=date(2019, 4, 1),
receptor_rut=Rut('96790240-3'),
monto_total=2996301,
emisor_razon_social='INGENIERIA ENACON SPA',
receptor_razon_social='MINERA LOS PELAMBRES',
fecha_vencimiento_date=None,
firma_documento_dt=tz_utils.convert_naive_dt_to_tz_aware(
dt=datetime(2019, 4, 1, 1, 36, 40),
tz=DteDataL2.DATETIME_FIELDS_TZ),
signature_value=self._TEST_DTE_1_SIGNATURE_VALUE,
signature_x509_cert_der=self.dte_clean_xml_1_cert_der,
emisor_giro='Ingenieria y Construccion',
emisor_email=None,
receptor_email=None,
))

def test_parse_dte_xml_ok_2(self) -> None:
xml_doc = xml_utils.parse_untrusted_xml(self.dte_clean_xml_2_xml_bytes)

Expand Down

0 comments on commit dba26db

Please sign in to comment.