From 4e2784fd45c5980f619eb2ce55c181a6ae7c297f Mon Sep 17 00:00:00 2001 From: Samuel Villegas Date: Wed, 24 Dec 2025 11:15:25 -0300 Subject: [PATCH] fix(rcv): Update RV fields optional in schemas and data models - Some valid RCV files were missing previously required fields - Updated various monetary and other fields (e.g., `monto_neto`, `monto_iva`) to be nullable. - Adjusted test cases and test data to reflect schema changes. Ref: https://app.shortcut.com/cordada/story/17695/ --- src/cl_sii/rcv/data_models.py | 26 +- src/cl_sii/rcv/parse_csv.py | 39 ++- .../RCV-venta-missing-required-fields.csv | 1 + src/tests/test_rcv_parse_csv.py | 254 +++++++++++++++++- 4 files changed, 285 insertions(+), 35 deletions(-) diff --git a/src/cl_sii/rcv/data_models.py b/src/cl_sii/rcv/data_models.py index acdf4ba6..8188b0cf 100644 --- a/src/cl_sii/rcv/data_models.py +++ b/src/cl_sii/rcv/data_models.py @@ -344,17 +344,17 @@ class RvDetalleEntry(RcvDetalleEntry): Fecha Reclamo (must be timezone aware) """ - monto_exento: int + monto_exento: Optional[int] """ Monto Exento """ - monto_neto: int + monto_neto: Optional[int] """ Monto Neto """ - monto_iva: int + monto_iva: Optional[int] """ Monto IVA """ @@ -389,22 +389,22 @@ class RvDetalleEntry(RcvDetalleEntry): RUT Emisor Liquid. Factura """ - neto_comision_liquidacion_factura: int + neto_comision_liquidacion_factura: Optional[int] """ Neto Comision Liquid. Factura """ - exento_comision_liquidacion_factura: int + exento_comision_liquidacion_factura: Optional[int] """ Exento Comision Liquid. Factura """ - iva_comision_liquidacion_factura: int + iva_comision_liquidacion_factura: Optional[int] """ IVA Comision Liquid. Factura """ - iva_fuera_de_plazo: int + iva_fuera_de_plazo: Optional[int] """ IVA fuera de plazo """ @@ -429,7 +429,7 @@ class RvDetalleEntry(RcvDetalleEntry): Nacionalidad Receptor Extranjero """ - credito_empresa_constructora: int + credito_empresa_constructora: Optional[int] """ Credito empresa constructora """ @@ -439,27 +439,27 @@ class RvDetalleEntry(RcvDetalleEntry): Impto. Zona Franca (Ley 18211) """ - garantia_dep_envases: int + garantia_dep_envases: Optional[int] """ Garantia Dep. Envases """ - indicador_venta_sin_costo: int + indicador_venta_sin_costo: Optional[int] """ Indicador Venta sin Costo """ - indicador_servicio_periodico: int + indicador_servicio_periodico: Optional[int] """ Indicador Servicio Periodico """ - monto_no_facturable: int + monto_no_facturable: Optional[int] """ Monto No facturable """ - total_monto_periodo: int + total_monto_periodo: Optional[int] """ Total Monto Periodo """ diff --git a/src/cl_sii/rcv/parse_csv.py b/src/cl_sii/rcv/parse_csv.py index 47a64287..674b75d6 100644 --- a/src/cl_sii/rcv/parse_csv.py +++ b/src/cl_sii/rcv/parse_csv.py @@ -523,15 +523,18 @@ class RcvVentaCsvRowSchema(_RcvCsvRowSchemaBase): data_key='Fecha Reclamo', ) monto_exento = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Monto Exento', ) monto_neto = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Monto Neto', ) monto_iva = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Monto IVA', ) monto_total = marshmallow.fields.Integer( @@ -569,19 +572,23 @@ class RcvVentaCsvRowSchema(_RcvCsvRowSchemaBase): data_key='RUT Emisor Liquid. Factura', ) neto_comision_liquidacion_factura = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Neto Comision Liquid. Factura', ) exento_comision_liquidacion_factura = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Exento Comision Liquid. Factura', ) iva_comision_liquidacion_factura = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='IVA Comision Liquid. Factura', ) iva_fuera_de_plazo = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='IVA fuera de plazo', ) tipo_documento_referencia = marshmallow.fields.Integer( @@ -605,7 +612,8 @@ class RcvVentaCsvRowSchema(_RcvCsvRowSchemaBase): data_key='Nacionalidad Receptor Extranjero', ) credito_empresa_constructora = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Credito empresa constructora', ) impuesto_zona_franca_ley_18211 = marshmallow.fields.Integer( @@ -614,23 +622,28 @@ class RcvVentaCsvRowSchema(_RcvCsvRowSchemaBase): data_key='Impto. Zona Franca (Ley 18211)', ) garantia_dep_envases = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Garantia Dep. Envases', ) indicador_venta_sin_costo = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Indicador Venta sin Costo', ) indicador_servicio_periodico = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Indicador Servicio Periodico', ) monto_no_facturable = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Monto No facturable', ) total_monto_periodo = marshmallow.fields.Integer( - required=True, + required=False, + allow_none=True, data_key='Total Monto Periodo', ) venta_pasajes_transporte_nacional = marshmallow.fields.Integer( diff --git a/src/tests/test_data/sii-rcv/RCV-venta-missing-required-fields.csv b/src/tests/test_data/sii-rcv/RCV-venta-missing-required-fields.csv index 60718859..bff6f576 100644 --- a/src/tests/test_data/sii-rcv/RCV-venta-missing-required-fields.csv +++ b/src/tests/test_data/sii-rcv/RCV-venta-missing-required-fields.csv @@ -1,3 +1,4 @@ Nro;Tipo Doc;Tipo Venta;Rut cliente;Razon Social;Folio;Fecha Docto;Fecha Recepcion;Fecha Acuse Recibo;Fecha Reclamo;Monto Exento;Monto Neto;Monto IVA;Monto total;IVA Retenido Total;IVA Retenido Parcial;IVA no retenido;IVA propio;IVA Terceros;RUT Emisor Liquid. Factura;Neto Comision Liquid. Factura;Exento Comision Liquid. Factura;IVA Comision Liquid. Factura;IVA fuera de plazo;Tipo Docto. Referencia;Folio Docto. Referencia;Num. Ident. Receptor Extranjero;Nacionalidad Receptor Extranjero;Credito empresa constructora;Impto. Zona Franca (Ley 18211);Garantia Dep. Envases;Indicador Venta sin Costo;Indicador Servicio Periodico;Monto No facturable;Total Monto Periodo;Venta Pasajes Transporte Nacional;Venta Pasajes Transporte Internacional;Numero Interno;Codigo Sucursal;NCE o NDE sobre Fact. de Compra;Codigo Otro Imp.;Valor Otro Imp.;Tasa Otro Imp. 1;"";Del Giro;12345678-5;Fake Company S.A. ;506;04/06/2019;"";;;0;1750181;332534;2082715;0;0;0;0;0;-;0;0;0;0;;;;;0;;0;2;0;0;0;;;;0;;;;; 23;33;Del Giro;12345678-5; Fake Company S.A.;508;28/06/2019;01/07/2019 13:49:42;;;0;2209597;419823;2629420;0;0;0;0;0;-;0;0;0;0;0;;;;0;;0;2;0;0;0;;;;0;;;;; +3;30;Del Giro;4954153-8;Faker Company;88;04/08/2017;13/09/2017 10:18:59;;;;15915315;3023910;18939225;;;;;;-;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/tests/test_rcv_parse_csv.py b/src/tests/test_rcv_parse_csv.py index e81260b6..0cc713ea 100644 --- a/src/tests/test_rcv_parse_csv.py +++ b/src/tests/test_rcv_parse_csv.py @@ -531,15 +531,251 @@ def test_parse_rcv_venta_csv_file_missing_required_fields(self) -> None: ) assert isinstance(items, Iterable) and isinstance(items, Iterator) - entry_struct, row_ix, row_data, row_parsing_errors = next(items) - self.assertIsNone(entry_struct) - self.assertIn('validation', row_parsing_errors) - self.assertIn('Fecha Recepcion', row_parsing_errors['validation']) - self.assertIn('Tipo Doc', row_parsing_errors['validation']) - self.assertEqual( - row_parsing_errors['validation']['Fecha Recepcion'], - ['Missing data for required field.'], - ) + items_list = list(items) + + expected_entries_list: list[ + tuple[Optional[RvDetalleEntry], int, dict[str, object], dict[str, object]] + ] + expected_entries_list = [ + ( + None, + 1, + { + 'Tipo Venta': 'DEL_GIRO', + 'Rut cliente': '12345678-5', + 'Razon Social': 'Fake Company S.A. ', + 'Folio': '506', + 'Fecha Docto': '04/06/2019', + 'Fecha Acuse Recibo': None, + 'Fecha Reclamo': None, + 'Monto Exento': '0', + 'Monto Neto': '1750181', + 'Monto IVA': '332534', + 'Monto total': '2082715', + 'IVA Retenido Total': '0', + 'IVA Retenido Parcial': '0', + 'IVA no retenido': '0', + 'IVA propio': '0', + 'IVA Terceros': '0', + 'RUT Emisor Liquid. Factura': None, + 'Neto Comision Liquid. Factura': '0', + 'Exento Comision Liquid. Factura': '0', + 'IVA Comision Liquid. Factura': '0', + 'IVA fuera de plazo': '0', + 'Tipo Docto. Referencia': None, + 'Folio Docto. Referencia': None, + 'Num. Ident. Receptor Extranjero': None, + 'Nacionalidad Receptor Extranjero': None, + 'Credito empresa constructora': '0', + 'Impto. Zona Franca (Ley 18211)': None, + 'Garantia Dep. Envases': '0', + 'Indicador Venta sin Costo': '2', + 'Indicador Servicio Periodico': '0', + 'Monto No facturable': '0', + 'Total Monto Periodo': '0', + 'Venta Pasajes Transporte Nacional': None, + 'Venta Pasajes Transporte Internacional': None, + 'Numero Interno': None, + 'Codigo Sucursal': '0', + 'NCE o NDE sobre Fact. de Compra': None, + 'Otros Impuestos': None, + 'contribuyente_rut': Rut('1-9'), + }, + { + 'validation': { + 'Tipo Doc': ['Missing data for required field.'], + 'Fecha Recepcion': ['Missing data for required field.'], + } + }, + ), + ( + RvDetalleEntry( + contribuyente_rut=Rut('1-9'), + tipo_docto=cl_sii.rcv.constants.RcvTipoDocto.FACTURA_ELECTRONICA, + folio=508, + fecha_emision_date=datetime.date(2019, 6, 28), + monto_total=2629420, + fecha_recepcion_dt=convert_naive_dt_to_tz_aware( + dt=datetime.datetime(2019, 7, 1, 13, 49, 42), + tz=SII_OFFICIAL_TZ, + ), + cliente_rut=Rut('12345678-5'), + tipo_venta='DEL_GIRO', + cliente_razon_social='Fake Company S.A.', + fecha_acuse_dt=None, + fecha_reclamo_dt=None, + monto_exento=0, + monto_neto=2209597, + monto_iva=419823, + iva_retenido_total=0, + iva_retenido_parcial=0, + iva_no_retenido=0, + iva_propio=0, + iva_terceros=0, + liquidacion_factura_emisor_rut=None, + neto_comision_liquidacion_factura=0, + exento_comision_liquidacion_factura=0, + iva_comision_liquidacion_factura=0, + iva_fuera_de_plazo=0, + tipo_documento_referencia=0, + folio_documento_referencia=None, + num_ident_receptor_extranjero=None, + nacionalidad_receptor_extranjero=None, + credito_empresa_constructora=0, + impuesto_zona_franca_ley_18211=None, + garantia_dep_envases=0, + indicador_venta_sin_costo=2, + indicador_servicio_periodico=0, + monto_no_facturable=0, + total_monto_periodo=0, + venta_pasajes_transporte_nacional=None, + venta_pasajes_transporte_internacional=None, + numero_interno=None, + codigo_sucursal='0', + nce_o_nde_sobre_factura_de_compra=None, + otros_impuestos=None, + ), + 2, + { + 'Tipo Doc': '33', + 'Tipo Venta': 'DEL_GIRO', + 'Rut cliente': '12345678-5', + 'Razon Social': ' Fake Company S.A.', + 'Folio': '508', + 'Fecha Docto': '28/06/2019', + 'Fecha Recepcion': '01/07/2019 13:49:42', + 'Fecha Acuse Recibo': None, + 'Fecha Reclamo': None, + 'Monto Exento': '0', + 'Monto Neto': '2209597', + 'Monto IVA': '419823', + 'Monto total': '2629420', + 'IVA Retenido Total': '0', + 'IVA Retenido Parcial': '0', + 'IVA no retenido': '0', + 'IVA propio': '0', + 'IVA Terceros': '0', + 'RUT Emisor Liquid. Factura': None, + 'Neto Comision Liquid. Factura': '0', + 'Exento Comision Liquid. Factura': '0', + 'IVA Comision Liquid. Factura': '0', + 'IVA fuera de plazo': '0', + 'Tipo Docto. Referencia': '0', + 'Folio Docto. Referencia': None, + 'Num. Ident. Receptor Extranjero': None, + 'Nacionalidad Receptor Extranjero': None, + 'Credito empresa constructora': '0', + 'Impto. Zona Franca (Ley 18211)': None, + 'Garantia Dep. Envases': '0', + 'Indicador Venta sin Costo': '2', + 'Indicador Servicio Periodico': '0', + 'Monto No facturable': '0', + 'Total Monto Periodo': '0', + 'Venta Pasajes Transporte Nacional': None, + 'Venta Pasajes Transporte Internacional': None, + 'Numero Interno': None, + 'Codigo Sucursal': '0', + 'NCE o NDE sobre Fact. de Compra': None, + 'Otros Impuestos': None, + 'contribuyente_rut': Rut('1-9'), + }, + {}, + ), + ( + RvDetalleEntry( + contribuyente_rut=Rut('1-9'), + tipo_docto=cl_sii.rcv.constants.RcvTipoDocto.FACTURA, + folio=88, + fecha_emision_date=datetime.date(2017, 8, 4), + monto_total=18939225, + fecha_recepcion_dt=convert_naive_dt_to_tz_aware( + dt=datetime.datetime(2017, 9, 13, 10, 18, 59), + tz=SII_OFFICIAL_TZ, + ), + cliente_rut=Rut('4954153-8'), + tipo_venta='DEL_GIRO', + cliente_razon_social='Faker Company', + fecha_acuse_dt=None, + fecha_reclamo_dt=None, + monto_exento=None, + monto_neto=15915315, + monto_iva=3023910, + iva_retenido_total=None, + iva_retenido_parcial=None, + iva_no_retenido=None, + iva_propio=None, + iva_terceros=None, + liquidacion_factura_emisor_rut=None, + neto_comision_liquidacion_factura=None, + exento_comision_liquidacion_factura=None, + iva_comision_liquidacion_factura=None, + iva_fuera_de_plazo=None, + tipo_documento_referencia=None, + folio_documento_referencia=None, + num_ident_receptor_extranjero=None, + nacionalidad_receptor_extranjero=None, + credito_empresa_constructora=None, + impuesto_zona_franca_ley_18211=None, + garantia_dep_envases=None, + indicador_venta_sin_costo=None, + indicador_servicio_periodico=None, + monto_no_facturable=None, + total_monto_periodo=None, + venta_pasajes_transporte_nacional=None, + venta_pasajes_transporte_internacional=None, + numero_interno=None, + codigo_sucursal=None, + nce_o_nde_sobre_factura_de_compra=None, + otros_impuestos=None, + ), + 3, + { + 'Codigo Sucursal': None, + 'Credito empresa constructora': None, + 'Exento Comision Liquid. Factura': None, + 'Fecha Acuse Recibo': None, + 'Fecha Docto': '04/08/2017', + 'Fecha Recepcion': '13/09/2017 10:18:59', + 'Fecha Reclamo': None, + 'Folio': '88', + 'Folio Docto. Referencia': None, + 'Garantia Dep. Envases': None, + 'IVA Comision Liquid. Factura': None, + 'IVA Retenido Parcial': None, + 'IVA Retenido Total': None, + 'IVA Terceros': None, + 'IVA fuera de plazo': None, + 'IVA no retenido': None, + 'IVA propio': None, + 'Impto. Zona Franca (Ley 18211)': None, + 'Indicador Servicio Periodico': None, + 'Indicador Venta sin Costo': None, + 'Monto Exento': None, + 'Monto IVA': '3023910', + 'Monto Neto': '15915315', + 'Monto No facturable': None, + 'Monto total': '18939225', + 'NCE o NDE sobre Fact. de Compra': None, + 'Nacionalidad Receptor Extranjero': None, + 'Neto Comision Liquid. Factura': None, + 'Num. Ident. Receptor Extranjero': None, + 'Numero Interno': None, + 'Otros Impuestos': None, + 'RUT Emisor Liquid. Factura': None, + 'Razon Social': 'Faker Company', + 'Rut cliente': '4954153-8', + 'Tipo Doc': '30', + 'Tipo Docto. Referencia': None, + 'Tipo Venta': 'DEL_GIRO', + 'Total Monto Periodo': None, + 'Venta Pasajes Transporte Internacional': None, + 'Venta Pasajes Transporte Nacional': None, + 'contribuyente_rut': Rut('1-9'), + }, + {}, + ), + ] + self.assertEqual(items_list, expected_entries_list) def _test_parse_rcv_compra_csv_file_proveedor_rz_leading_trailing_whitespace( self,