Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.8.4
current_version = 0.9.1
commit = True
tag = True

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

0.9.1 (2020-03-20)
+++++++++++++++++++++++

* Fix incorrect version used in the previous release's changelog.

0.9.0 (2020-03-20)
+++++++++++++++++++++++

* (PR #104, 2020-02-27) cte.f29.parser: Rename custom validator and deserializer parameters
* (PR #97, 2020-02-25) cte: Allow four digit Form 29 codes
* (PR #103, 2020-02-24) cte: Add custom validators and deserializers to parser

0.8.4 (2020-02-06)
+++++++++++++++++++++++

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.8.4'
__version__ = '0.9.1'
2 changes: 2 additions & 0 deletions cl_sii/cte/f29/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class CteForm29:
586: None, # "CANT. VTAS. Y/O SERV. PREST. INT. EXENT."
587: None, # Facturas de Compra recibidas c/ret. total y Fact. de Inicio emitida | Monto
595: None, # "SUB TOTAL IMP. DETERMINADO ANVERSO"
596: None, # "RETENCION CAMBIO DE SUJETO"
601: None, # Fax
610: 'numero_direccion', # Nº Dirección
708: None, # "CANT. NOTAS CRED. EMIT. VALES MAQ. IVA"
Expand All @@ -167,6 +168,7 @@ class CteForm29:
795: None, # "MONTO DE CONDONACION SII"
915: 'fecha_condonacion', # "Fecha de Vigencia de Condonacion"
922: 'num_res_condonacion', # "NUMERO RESOLUCION SII AUTO. CONDONACION"
9906: None, # "FECHA PRESENTACION DECL. PRIMITIVA"
}

def __post_init__(self) -> None:
Expand Down
59 changes: 51 additions & 8 deletions cl_sii/cte/f29/parse_datos_obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import datetime
from decimal import Decimal
from pathlib import Path
from typing import Mapping, MutableMapping
from typing import Callable, Mapping, MutableMapping, Optional

import jsonschema

Expand All @@ -22,27 +22,50 @@
CTE_F29_DATOS_OBJ_SCHEMA = read_json_schema(_CTE_F29_DATOS_OBJ_SCHEMA_PATH)


def parse_sii_cte_f29_datos_obj(datos_obj: SiiCteF29DatosObjType) -> CteForm29:
def parse_sii_cte_f29_datos_obj(
datos_obj: SiiCteF29DatosObjType,
schema_validator: Optional[Callable[[SiiCteF29DatosObjType], None]] = None,
campo_deserializer: Optional[Callable[[object, str], object]] = None,
) -> CteForm29:
"""
Parse the ``datos`` JavaScript object embedded in the IFrames of the
HTML version of the CTE's 'Declaraciones de IVA (F29)'.

:param schema_validator: Schema validator. For the signature of the callable, see the docstring
of ``cte_f29_datos_schema_default_validator``.
:param campo_deserializer: 'Campo' deserializer. For the signature of the callable, see the
docstring of ``cte_f29_datos_obj_campo_default_deserializer``.
:raises JsonSchemaValidationError: If schema validation fails.
"""
obj_params = _parse_sii_cte_f29_datos_obj_to_dict(datos_obj=datos_obj)
if schema_validator is None:
schema_validator = cte_f29_datos_schema_default_validator
if campo_deserializer is None:
campo_deserializer = cte_f29_datos_obj_campo_default_deserializer

obj_params = _parse_sii_cte_f29_datos_obj_to_dict(
datos_obj=datos_obj,
schema_validator=schema_validator,
campo_deserializer=campo_deserializer,
)
obj = CteForm29(**obj_params)
return obj


def _parse_sii_cte_f29_datos_obj_to_dict(datos_obj: SiiCteF29DatosObjType) -> Mapping[str, object]:
def _parse_sii_cte_f29_datos_obj_to_dict(
datos_obj: SiiCteF29DatosObjType,
schema_validator: Callable[[SiiCteF29DatosObjType], None],
campo_deserializer: Callable[[object, str], object],
) -> Mapping[str, object]:
"""
Parse the ``datos`` JavaScript object embedded in the IFrames of the
HTML version of the CTE's 'Declaraciones de IVA (F29)' and return a
dictionary.

:param schema_validator:
:param campo_deserializer:
:raises JsonSchemaValidationError: If schema validation fails.
"""
_validate_cte_f29_datos_schema(datos_obj=datos_obj)
schema_validator(datos_obj)

datos_obj_campos: Mapping[int, str] = {
int(code): str(value) for code, value in datos_obj['campos'].items()
Expand All @@ -56,7 +79,7 @@ def _parse_sii_cte_f29_datos_obj_to_dict(datos_obj: SiiCteF29DatosObjType) -> Ma
}

deserialized_datos_obj_campos = {
code: _deserialize_cte_f29_datos_obj_campo(campo_value=value, tipo=datos_obj_tipos[code])
code: campo_deserializer(value, datos_obj_tipos[code])
for code, value in datos_obj_campos.items()
}

Expand All @@ -82,13 +105,14 @@ def _parse_sii_cte_f29_datos_obj_to_dict(datos_obj: SiiCteF29DatosObjType) -> Ma
return obj_dict


def _deserialize_cte_f29_datos_obj_campo(campo_value: object, tipo: str) -> object:
def cte_f29_datos_obj_campo_default_deserializer(campo_value: object, tipo: str) -> object:
"""
Convert raw values from the ``datos`` object to the corresponding Python
data type.

:param campo_value: Raw value from 'campos'.
:param tipo: Data type code from 'tipos'.
:raises ValueError: If value conversion fails.
"""
if not isinstance(campo_value, str):
return campo_value
Expand All @@ -111,7 +135,26 @@ def _deserialize_cte_f29_datos_obj_campo(campo_value: object, tipo: str) -> obje
return deserialized_value


def _validate_cte_f29_datos_schema(datos_obj: SiiCteF29DatosObjType) -> None:
def cte_f29_datos_obj_campo_best_effort_deserializer(campo_value: object, tipo: str) -> object:
"""
Convert raw values from the ``datos`` object to the corresponding Python
data type. Values that cannot be converted will be returned without modification.

:param campo_value: Raw value from 'campos'.
:param tipo: Data type code from 'tipos'.
"""
try:
deserialized_value = cte_f29_datos_obj_campo_default_deserializer(
campo_value=campo_value,
tipo=tipo,
)
except Exception:
deserialized_value = campo_value

return deserialized_value


def cte_f29_datos_schema_default_validator(datos_obj: SiiCteF29DatosObjType) -> None:
"""
Validate the ``datos`` object against the schema.

Expand Down
10 changes: 5 additions & 5 deletions cl_sii/data/cte/schemas-json/f29_datos_obj.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"campos": {
"type": "object",
"patternProperties": {
"^\\d{3}$": {
"^\\d{3,4}$": {
"type": "string"
}
},
Expand Down Expand Up @@ -108,7 +108,7 @@
"glosa": {
"type": "object",
"patternProperties": {
"^\\d{3}$": {
"^\\d{3,4}$": {
"type": "string"
}
},
Expand All @@ -117,7 +117,7 @@
"justif": {
"type": "object",
"patternProperties": {
"^\\d{3}$": {
"^\\d{3,4}$": {
"type": "string",
"enum": [
"I",
Expand All @@ -131,7 +131,7 @@
"linea": {
"type": "object",
"patternProperties": {
"^\\d{3}$": {
"^\\d{3,4}$": {
"type": "string",
"pattern": "^\\d+$"
}
Expand All @@ -141,7 +141,7 @@
"tipos": {
"type": "object",
"patternProperties": {
"^\\d{3}$": {
"^\\d{3,4}$": {
"type": "string",
"enum": [
"C",
Expand Down
104 changes: 104 additions & 0 deletions tests/test_cte_f29_parse_datos_obj.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import datetime
from decimal import Decimal
from typing import Any, Mapping
from unittest import TestCase

Expand Down Expand Up @@ -114,3 +115,106 @@ def test_full(self) -> None:
"fecha_presentacion": datetime.date(2017, 8, 28),
}
self.assertEqual(dict(obj), expected_dict)

def test_full_2(self) -> None:
datos_obj: Mapping[str, Any] = read_test_file_json_dict(
'test_data/sii-cte/f29/cte--6286736-1--f29-6700000056-datos-obj-fake.json',
)
obj = parse_datos_obj.parse_sii_cte_f29_datos_obj(
datos_obj=datos_obj,
campo_deserializer=parse_datos_obj.cte_f29_datos_obj_campo_best_effort_deserializer,
)

self.assertIsInstance(obj, data_models.CteForm29)
self.assertEqual(obj.contribuyente_rut, Rut('6286736-1'))
self.assertEqual(obj.periodo_tributario, PeriodoTributario(year=2018, month=12))
self.assertEqual(obj.folio, 6700000056)

expected_codes_dict = {
1: "BONVALLET",
2: "GODOY",
3: Rut('6286736-1'),
5: "EDWARD GUILLERMO",
6: "GRECIA 2001",
7: 6700000056,
8: "MELIPILLA",
9: '0',
15: PeriodoTributario(year=2018, month=12),
48: 22633,
55: "E.BONVALLET@EXAMPLE.COM",
62: 32875,
77: 584976,
91: 503286,
115: Decimal('0.25'),
142: 13000000,
151: 447778,
315: datetime.date(2019, 1, 22),
502: 28500,
503: 1,
511: 613476,
519: 37,
520: 613476,
521: 2365382,
537: 613476,
538: 28500,
547: 503286,
562: 518660,
563: 13150000,
564: 27,
584: 11,
586: 1,
595: 503286,
596: 0,
9906: "22/01/2019",
}
self.assertEqual(obj.as_codes_dict(include_none=False), expected_codes_dict)

expected_dict = {
"contribuyente_rut": Rut('6286736-1'),
"periodo_tributario": PeriodoTributario(year=2018, month=12),
"folio": 6700000056,
"apellido_paterno_o_razon_social": "BONVALLET",
"apellido_materno": "GODOY",
"nombres": "EDWARD GUILLERMO",
"calle_direccion": "GRECIA 2001",
"numero_direccion": None,
"comuna_direccion": "MELIPILLA",
"telefono": "0",
"correo_electronico": "E.BONVALLET@EXAMPLE.COM",
"representante_legal_rut": None,
"extra": {
48: 22633,
62: 32875,
77: 584976,
115: Decimal('0.25'),
142: 13000000,
151: 447778,
502: 28500,
503: 1,
511: 613476,
519: 37,
520: 613476,
521: 2365382,
537: 613476,
538: 28500,
547: 503286,
562: 518660,
563: 13150000,
564: 27,
584: 11,
586: 1,
595: 503286,
596: 0,
9906: "22/01/2019",
},
"total_a_pagar_en_plazo_legal": 503286,
"total_a_pagar_con_recargo": None,
"pct_condonacion": None,
"num_res_condonacion": None,
"fecha_condonacion": None,
"tipo_declaracion": "Rectificatoria con Giro",
"banco": "",
"medio_pago": "",
"fecha_presentacion": datetime.date(2019, 1, 22),
}
self.assertEqual(dict(obj), expected_dict)
Loading