From 9096a3a70bb94214387ecd55708eea009ed44839 Mon Sep 17 00:00:00 2001 From: Samuel Villegas Date: Mon, 15 Sep 2025 12:39:13 -0300 Subject: [PATCH] fix(rcv): Improve checks for RCV Reclamado parser workaround - Updated `RcvCompraReclamadoCsvRowSchema.preprocess` workaround for `Fecha Reclamo` to check if variable is already `None` - Implemented test `test_parse_rcv_compra_reclamado_csv_file` to check multiple use cases for parser `RcvCompraReclamadoCsvRowSchema` Ref: https://app.shortcut.com/cordada/story/16601/ --- src/cl_sii/rcv/parse_csv.py | 5 +- .../sii-rcv/RCV-compra-reclamado.csv | 6 + src/tests/test_rcv_parse_csv.py | 281 +++++++++++++++++- 3 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 src/tests/test_data/sii-rcv/RCV-compra-reclamado.csv diff --git a/src/cl_sii/rcv/parse_csv.py b/src/cl_sii/rcv/parse_csv.py index 28a40176..e29deb2b 100644 --- a/src/cl_sii/rcv/parse_csv.py +++ b/src/cl_sii/rcv/parse_csv.py @@ -1263,8 +1263,9 @@ def preprocess(self, in_data: dict, **kwargs: Any) -> dict: # note: for some reason the rows with 'tipo_docto' equal to # '' (and maybe others as well) do not # have this field set (always? we do not know). - if 'Fecha Reclamo' in in_data: - if in_data['Fecha Reclamo'] == '' or 'null' in in_data['Fecha Reclamo']: + if 'Fecha Reclamo' in in_data and in_data['Fecha Reclamo'] is not None: + value = in_data['Fecha Reclamo'] + if isinstance(value, str) and (value == '' or 'null' in value): in_data['Fecha Reclamo'] = None return in_data diff --git a/src/tests/test_data/sii-rcv/RCV-compra-reclamado.csv b/src/tests/test_data/sii-rcv/RCV-compra-reclamado.csv new file mode 100644 index 00000000..6e08cf8d --- /dev/null +++ b/src/tests/test_data/sii-rcv/RCV-compra-reclamado.csv @@ -0,0 +1,6 @@ +Nro;Tipo Doc;Tipo Compra;RUT Proveedor;Razon Social;Folio;Fecha Docto;Fecha Recepcion;Fecha Reclamo;Monto Exento;Monto Neto;Monto IVA Recuperable;Monto Iva No Recuperable;Codigo IVA No Rec.;Monto Total;Monto Neto Activo Fijo;IVA Activo Fijo;IVA uso Comun;Impto. Sin Derecho a Credito;IVA No Retenido;NCE o NDE sobre Fact. de Compra;Codigo Otro Impuesto;Valor Otro Impuesto;Tasa Otro Impuesto +1;33;Del Giro;12345678-5;Fake Company S.A. ;1000055;05/06/2019;05/06/2019 21:58:49;12/06/2019 09:47:23;0;970894;184470;;;1155364;;;;;0;0;;;; +2;61;Del Giro;12345678-5; Fake Company S.A.;70013;24/06/2019;24/06/2019 15:24:41;null;0;1652840;314040;;;1966880;;;;;0;0;;;; +3;33;Del Giro;76354771-K;Fake Company S.A. ;789456;05/06/2019;05/06/2019 21:58:49;;0;970894;184470;;;1155364;;;;;0;0;;;; +4;33;Del Giro;INVALID-RUT;Fake Company S.A.;notanumber;invalid-date;invalid-datetime;invalid-datetime;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber;notanumber +5;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/tests/test_rcv_parse_csv.py b/src/tests/test_rcv_parse_csv.py index f03137ba..f37dd520 100644 --- a/src/tests/test_rcv_parse_csv.py +++ b/src/tests/test_rcv_parse_csv.py @@ -597,8 +597,285 @@ def test_parse_rcv_compra_no_incluir_csv_file_emisor_rz_leading_trailing_whitesp ) def test_parse_rcv_compra_reclamado_csv_file(self) -> None: - # TODO: implement for 'parse_rcv_compra_reclamado_csv_file'. - pass + rcv_file_path = get_test_file_path('test_data/sii-rcv/RCV-compra-reclamado.csv') + + items = parse_rcv_compra_reclamado_csv_file( + rut=Rut('1-9'), + input_file_path=rcv_file_path, + n_rows_offset=0, + max_n_rows=None, + ) + result = list(items) + + # Expected output: list of RcReclamadoDetalleEntry instances matching the CSV + expected_result = [ + ( + RcReclamadoDetalleEntry( + emisor_rut=Rut('12345678-5'), + tipo_docto=cl_sii.rcv.constants.RcvTipoDocto.FACTURA_ELECTRONICA, + folio=1000055, + fecha_emision_date=datetime.date(2019, 6, 5), + receptor_rut=Rut('1-9'), + monto_total=1155364, + fecha_recepcion_dt=convert_naive_dt_to_tz_aware( + dt=datetime.datetime(2019, 6, 5, 21, 58, 49), + tz=SII_OFFICIAL_TZ, + ), + tipo_compra='DEL_GIRO', + emisor_razon_social='Fake Company S.A.', + monto_exento=0, + monto_neto=970894, + monto_iva_recuperable=184470, + monto_iva_no_recuperable=None, + codigo_iva_no_rec=None, + monto_neto_activo_fijo=None, + iva_activo_fijo=None, + iva_uso_comun=None, + impto_sin_derecho_a_credito=None, + iva_no_retenido=0, + nce_o_nde_sobre_factura_de_compra='0', + codigo_otro_impuesto=None, + valor_otro_impuesto=None, + tasa_otro_impuesto=None, + fecha_reclamo_dt=convert_naive_dt_to_tz_aware( + dt=datetime.datetime(2019, 6, 12, 9, 47, 23), + tz=SII_OFFICIAL_TZ, + ), + ), + 1, + { + 'Tipo Doc': '33', + 'Tipo Compra': 'DEL_GIRO', + 'RUT Proveedor': '12345678-5', + 'Razon Social': 'Fake Company S.A. ', + 'Folio': '1000055', + 'Fecha Docto': '05/06/2019', + 'Fecha Recepcion': '05/06/2019 21:58:49', + 'Fecha Reclamo': '12/06/2019 09:47:23', + 'Monto Exento': '0', + 'Monto Neto': '970894', + 'Monto IVA Recuperable': '184470', + 'Monto Iva No Recuperable': None, + 'Codigo IVA No Rec.': None, + 'Monto Total': '1155364', + 'Monto Neto Activo Fijo': None, + 'IVA Activo Fijo': None, + 'IVA uso Comun': None, + 'Impto. Sin Derecho a Credito': None, + 'IVA No Retenido': '0', + 'NCE o NDE sobre Fact. de Compra': '0', + 'Codigo Otro Impuesto': None, + 'Valor Otro Impuesto': None, + 'Tasa Otro Impuesto': None, + 'receptor_rut': Rut('1-9'), + }, + {}, + ), + ( + RcReclamadoDetalleEntry( + emisor_rut=Rut('12345678-5'), + tipo_docto=cl_sii.rcv.constants.RcvTipoDocto.NOTA_CREDITO_ELECTRONICA, + folio=70013, + fecha_emision_date=datetime.date(2019, 6, 24), + receptor_rut=Rut('1-9'), + monto_total=1966880, + fecha_recepcion_dt=convert_naive_dt_to_tz_aware( + dt=datetime.datetime(2019, 6, 24, 15, 24, 41), + tz=SII_OFFICIAL_TZ, + ), + tipo_compra='DEL_GIRO', + emisor_razon_social='Fake Company S.A.', + monto_exento=0, + monto_neto=1652840, + monto_iva_recuperable=314040, + monto_iva_no_recuperable=None, + codigo_iva_no_rec=None, + monto_neto_activo_fijo=None, + iva_activo_fijo=None, + iva_uso_comun=None, + impto_sin_derecho_a_credito=None, + iva_no_retenido=0, + nce_o_nde_sobre_factura_de_compra='0', + codigo_otro_impuesto=None, + valor_otro_impuesto=None, + tasa_otro_impuesto=None, + fecha_reclamo_dt=None, + ), + 2, + { + 'Tipo Doc': '61', + 'Tipo Compra': 'DEL_GIRO', + 'RUT Proveedor': '12345678-5', + 'Razon Social': ' Fake Company S.A.', + 'Folio': '70013', + 'Fecha Docto': '24/06/2019', + 'Fecha Recepcion': '24/06/2019 15:24:41', + 'Fecha Reclamo': None, + 'Monto Exento': '0', + 'Monto Neto': '1652840', + 'Monto IVA Recuperable': '314040', + 'Monto Iva No Recuperable': None, + 'Codigo IVA No Rec.': None, + 'Monto Total': '1966880', + 'Monto Neto Activo Fijo': None, + 'IVA Activo Fijo': None, + 'IVA uso Comun': None, + 'Impto. Sin Derecho a Credito': None, + 'IVA No Retenido': '0', + 'NCE o NDE sobre Fact. de Compra': '0', + 'Codigo Otro Impuesto': None, + 'Valor Otro Impuesto': None, + 'Tasa Otro Impuesto': None, + 'receptor_rut': Rut('1-9'), + }, + {}, + ), + ( + RcReclamadoDetalleEntry( + emisor_rut=Rut('76354771-K'), + tipo_docto=cl_sii.rcv.constants.RcvTipoDocto.FACTURA_ELECTRONICA, + folio=789456, + fecha_emision_date=datetime.date(2019, 6, 5), + receptor_rut=Rut('1-9'), + monto_total=1155364, + fecha_recepcion_dt=convert_naive_dt_to_tz_aware( + dt=datetime.datetime(2019, 6, 5, 21, 58, 49), + tz=SII_OFFICIAL_TZ, + ), + tipo_compra='DEL_GIRO', + emisor_razon_social='Fake Company S.A.', + monto_exento=0, + monto_neto=970894, + monto_iva_recuperable=184470, + monto_iva_no_recuperable=None, + codigo_iva_no_rec=None, + monto_neto_activo_fijo=None, + iva_activo_fijo=None, + iva_uso_comun=None, + impto_sin_derecho_a_credito=None, + iva_no_retenido=0, + nce_o_nde_sobre_factura_de_compra='0', + codigo_otro_impuesto=None, + valor_otro_impuesto=None, + tasa_otro_impuesto=None, + fecha_reclamo_dt=None, + ), + 3, + { + 'Tipo Doc': '33', + 'Tipo Compra': 'DEL_GIRO', + 'RUT Proveedor': '76354771-K', + 'Razon Social': 'Fake Company S.A. ', + 'Folio': '789456', + 'Fecha Docto': '05/06/2019', + 'Fecha Recepcion': '05/06/2019 21:58:49', + 'Fecha Reclamo': None, + 'Monto Exento': '0', + 'Monto Neto': '970894', + 'Monto IVA Recuperable': '184470', + 'Monto Iva No Recuperable': None, + 'Codigo IVA No Rec.': None, + 'Monto Total': '1155364', + 'Monto Neto Activo Fijo': None, + 'IVA Activo Fijo': None, + 'IVA uso Comun': None, + 'Impto. Sin Derecho a Credito': None, + 'IVA No Retenido': '0', + 'NCE o NDE sobre Fact. de Compra': '0', + 'Codigo Otro Impuesto': None, + 'Valor Otro Impuesto': None, + 'Tasa Otro Impuesto': None, + 'receptor_rut': Rut('1-9'), + }, + {}, + ), + ( + None, + 4, + { + 'Tipo Doc': '33', + 'Tipo Compra': 'DEL_GIRO', + 'RUT Proveedor': 'INVALID-RUT', + 'Razon Social': 'Fake Company S.A.', + 'Folio': 'notanumber', + 'Fecha Docto': 'invalid-date', + 'Fecha Recepcion': 'invalid-datetime', + 'Fecha Reclamo': 'invalid-datetime', + 'Monto Exento': 'notanumber', + 'Monto Neto': 'notanumber', + 'Monto IVA Recuperable': 'notanumber', + 'Monto Iva No Recuperable': 'notanumber', + 'Codigo IVA No Rec.': 'notanumber', + 'Monto Total': 'notanumber', + 'Monto Neto Activo Fijo': 'notanumber', + 'IVA Activo Fijo': 'notanumber', + 'IVA uso Comun': 'notanumber', + 'Impto. Sin Derecho a Credito': 'notanumber', + 'IVA No Retenido': 'notanumber', + 'NCE o NDE sobre Fact. de Compra': 'notanumber', + 'Codigo Otro Impuesto': 'notanumber', + 'Valor Otro Impuesto': 'notanumber', + 'Tasa Otro Impuesto': 'notanumber', + 'receptor_rut': Rut('1-9'), + }, + { + 'validation': { + 'RUT Proveedor': ['Not a syntactically valid RUT.'], + 'Folio': ['Not a valid integer.'], + 'Fecha Docto': ['Not a valid date.'], + 'Monto Total': ['Not a valid integer.'], + 'Fecha Recepcion': ['Not a valid datetime.'], + 'Monto Exento': ['Not a valid integer.'], + 'Monto Neto': ['Not a valid integer.'], + 'Monto IVA Recuperable': ['Not a valid integer.'], + 'Monto Iva No Recuperable': ['Not a valid integer.'], + 'Monto Neto Activo Fijo': ['Not a valid integer.'], + 'IVA Activo Fijo': ['Not a valid integer.'], + 'IVA uso Comun': ['Not a valid integer.'], + 'Impto. Sin Derecho a Credito': ['Not a valid integer.'], + 'IVA No Retenido': ['Not a valid integer.'], + 'Valor Otro Impuesto': ['Not a valid integer.'], + 'Tasa Otro Impuesto': ['Not a valid number.'], + 'Fecha Reclamo': ['Not a valid datetime.'], + } + }, + ), + ( + None, + 5, + { + 'Fecha Reclamo': None, + 'Monto IVA Recuperable': None, + 'Monto Iva No Recuperable': None, + 'Codigo IVA No Rec.': None, + 'Monto Neto Activo Fijo': None, + 'IVA Activo Fijo': None, + 'IVA uso Comun': None, + 'Impto. Sin Derecho a Credito': None, + 'IVA No Retenido': None, + 'NCE o NDE sobre Fact. de Compra': None, + 'Codigo Otro Impuesto': None, + 'Valor Otro Impuesto': None, + 'Tasa Otro Impuesto': None, + 'receptor_rut': Rut('1-9'), + }, + { + 'validation': { + 'RUT Proveedor': ['Missing data for required field.'], + 'Tipo Doc': ['Missing data for required field.'], + 'Tipo Compra': ['Missing data for required field.'], + 'Folio': ['Missing data for required field.'], + 'Fecha Docto': ['Missing data for required field.'], + 'Monto Total': ['Missing data for required field.'], + 'Razon Social': ['Missing data for required field.'], + 'Fecha Recepcion': ['Missing data for required field.'], + 'Monto Exento': ['Missing data for required field.'], + 'Monto Neto': ['Missing data for required field.'], + } + }, + ), + ] + self.assertEqual(result, expected_result) def test_parse_rcv_compra_reclamado_csv_file_emisor_rz_leading_trailing_whitespace( self,