def test_truncate_fecha_cesion_dt_to_minutes(self) -> None: self._set_obj_1() obj = self.obj_1 expected_fecha_cesion_dt = datetime.fromisoformat( '2020-12-31T22:33-03:00') self.assertEqual(expected_fecha_cesion_dt.second, 0) self.assertEqual(expected_fecha_cesion_dt.microsecond, 0) obj_with_microseconds = dataclasses.replace( obj, fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2020, 12, 31, 22, 33, 44, 555555), tz=CesionAltNaturalKey.DATETIME_FIELDS_TZ, ), ) obj_with_datetime_truncated = dataclasses.replace( obj, fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2020, 12, 31, 22, 33), tz=CesionAltNaturalKey.DATETIME_FIELDS_TZ, ), ) self.assertEqual(obj_with_microseconds.fecha_cesion_dt, expected_fecha_cesion_dt) self.assertEqual(obj_with_datetime_truncated.fecha_cesion_dt, expected_fecha_cesion_dt) self.assertEqual(obj_with_microseconds, obj_with_datetime_truncated)
def test_alt_natural_key(self) -> None: self._set_obj_1() self._set_obj_2() obj = self.obj_1 expected_output = CesionAltNaturalKey( dte_key=DteNaturalKey( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, ), cedente_rut=Rut('76354771-K'), cesionario_rut=Rut('76389992-6'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 1, 10, 22), tz=CesionAltNaturalKey.DATETIME_FIELDS_TZ, ), ) self.assertEqual(obj.alt_natural_key, expected_output) obj = self.obj_2 expected_output = CesionAltNaturalKey( dte_key=DteNaturalKey( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, ), cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57), tz=CesionAltNaturalKey.DATETIME_FIELDS_TZ, ), ) self.assertEqual(obj.alt_natural_key, expected_output)
def test_as_cesion_l2(self) -> None: self._set_obj_1() obj = self.obj_1 expected_output = CesionL2( dte_key=DteNaturalKey( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, ), seq=2, cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=CesionL2.DATETIME_FIELDS_TZ, ), monto_cedido=2996301, fecha_firma_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=CesionL2.DATETIME_FIELDS_TZ, ), dte_receptor_rut=Rut('96790240-3'), dte_fecha_emision=date(2019, 4, 1), dte_monto_total=2996301, fecha_ultimo_vencimiento=date(2019, 5, 1), cedente_razon_social='ST CAPITAL S.A.', cedente_email='*****@*****.**', cesionario_razon_social= 'Fondo de Inversión Privado Deuda y Facturas', cesionario_email='*****@*****.**', dte_emisor_razon_social='INGENIERIA ENACON SPA', dte_receptor_razon_social='MINERA LOS PELAMBRES', dte_deudor_email=None, cedente_declaracion_jurada= ('Se declara bajo juramento que ST CAPITAL S.A., RUT 76389992-6 ha puesto ' 'a disposicion del cesionario Fondo de Inversión Privado Deuda y Facturas, ' 'RUT 76598556-0, el documento validamente emitido al deudor MINERA LOS ' 'PELAMBRES, RUT 96790240-3.'), dte_fecha_vencimiento=None, contacto_nombre='ST Capital Servicios Financieros', contacto_telefono=None, contacto_email='*****@*****.**', ) obj_cesion_l2 = obj.as_cesion_l2() self.assertEqual(obj_cesion_l2, expected_output) self.assertEqual(obj_cesion_l2.natural_key, obj.natural_key) self.assertEqual(obj_cesion_l2.alt_natural_key, obj.alt_natural_key) self.assertEqual(obj_cesion_l2.dte_key, obj.dte.natural_key)
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_3(self) -> None: xml_doc = xml_utils.parse_untrusted_xml(self.dte_clean_xml_3_xml_bytes) parsed_dte = parse_dte_xml(xml_doc) self.assertDictEqual( dict(parsed_dte.as_dict()), dict( emisor_rut=Rut('60910000-1'), tipo_dte=cl_sii.dte.constants.TipoDteEnum.FACTURA_ELECTRONICA, folio=2336600, fecha_emision_date=date(2019, 8, 8), receptor_rut=Rut('76555835-2'), monto_total=10642, emisor_razon_social='Universidad de Chile', receptor_razon_social='FYNPAL SPA', fecha_vencimiento_date=date(2019, 8, 8), firma_documento_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 8, 9, 9, 41, 9), tz=DteDataL2.DATETIME_FIELDS_TZ), signature_value=self._TEST_DTE_3_SIGNATURE_VALUE, signature_x509_cert_der=self.dte_clean_xml_3_cert_der, emisor_giro= 'Corporación Educacional y Servicios Profesionales', 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) parsed_dte = parse_dte_xml(xml_doc) self.assertDictEqual( dict(parsed_dte.as_dict()), dict( emisor_rut=Rut('76399752-9'), tipo_dte=cl_sii.dte.constants.TipoDteEnum.FACTURA_ELECTRONICA, folio=25568, fecha_emision_date=date(2019, 3, 29), receptor_rut=Rut('96874030-K'), monto_total=230992, emisor_razon_social='COMERCIALIZADORA INNOVA MOBEL SPA', receptor_razon_social='EMPRESAS LA POLAR S.A.', fecha_vencimiento_date=None, firma_documento_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 3, 28, 13, 59, 52), tz=DteDataL2.DATETIME_FIELDS_TZ), signature_value=self._TEST_DTE_2_SIGNATURE_VALUE, signature_x509_cert_der=self.dte_clean_xml_2_cert_der, emisor_giro='COMERCIALIZACION DE PRODUCTOS PARA EL HOGAR', emisor_email='*****@*****.**', receptor_email=None, ))
def _set_obj_1(self) -> None: obj_dte_natural_key = DteNaturalKey( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, ) obj = CesionL1( dte_key=obj_dte_natural_key, seq=32, cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=CesionL1.DATETIME_FIELDS_TZ, ), monto_cedido=2996301, fecha_ultimo_vencimiento=date(2019, 5, 1), dte_fecha_emision=date(2019, 4, 1), dte_receptor_rut=Rut('96790240-3'), dte_monto_total=2996301, ) self.assertIsInstance(obj, CesionL1) self.obj_1_dte_natural_key = obj_dte_natural_key self.obj_1 = obj
def test_as_cesion_l2_ok_1(self) -> None: obj = CesionesPeriodoEntry(**self.valid_kwargs) expected_output = CesionL2( dte_key=cl_sii.dte.data_models.DteNaturalKey( emisor_rut=Rut('51532520-4'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=3608460, ), seq=None, cedente_rut=Rut('51532520-4'), cesionario_rut=Rut('96667560-8'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 3, 7, 13, 32), tz=CesionL2.DATETIME_FIELDS_TZ, ), monto_cedido=256357, dte_receptor_rut=Rut('75320502-0'), dte_fecha_emision=date(2019, 2, 11), dte_monto_total=256357, fecha_ultimo_vencimiento=date(2019, 4, 12), cedente_razon_social='MI CAMPITO SA', cedente_email='*****@*****.**', cesionario_razon_social='POBRES SERVICIOS FINANCIEROS S.A.', cesionario_email='[email protected],[email protected]', ) obj_cesion_l2 = obj.as_cesion_l2() self.assertEqual(obj_cesion_l2, expected_output) self.assertIsNone(obj_cesion_l2.natural_key) self.assertEqual(obj_cesion_l2.alt_natural_key.dte_key.emisor_rut, obj.dte_vendedor_rut) self.assertEqual(obj_cesion_l2.alt_natural_key.cedente_rut, obj.cedente_rut) self.assertEqual(obj_cesion_l2.alt_natural_key.cesionario_rut, obj.cesionario_rut) self.assertEqual(obj_cesion_l2.alt_natural_key.fecha_cesion_dt, obj.fecha_cesion_dt) self.assertEqual(obj_cesion_l2.dte_receptor_rut, obj.dte_deudor_rut)
def postprocess(self, data: dict) -> dict: # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_dt'] = tz_utils.convert_naive_dt_to_tz_aware( dt=data['fecha_recepcion_dt'], tz=self.FIELD_FECHA_RECEPCION_DT_TZ) # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13-03:00' # >>> data['fecha_recepcion_dt'].astimezone(pytz.UTC).isoformat() # '2018-10-23T04:54:13+00:00' if data['fecha_reclamo_dt']: data['fecha_reclamo_dt'] = tz_utils.convert_naive_dt_to_tz_aware( dt=data['fecha_reclamo_dt'], tz=self.FIELD_FECHA_RECLAMO_DT_TZ) # note: to express this value in another timezone (but the value does not change), do # `dt_obj.astimezone(pytz.timezone('some timezone'))` return data
def test_as_dte_data_l2(self) -> None: self.assertEqual( self.dte_xml_data_1.as_dte_data_l2(), DteDataL2( emisor_rut=Rut('76354771-K'), tipo_dte=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=DteXmlData.DATETIME_FIELDS_TZ), signature_value=self.dte_1_xml_signature_value, signature_x509_cert_der=self.dte_1_xml_cert_der, emisor_giro='Ingenieria y Construccion', emisor_email='*****@*****.**', receptor_email=None, )) self.assertEqual( self.dte_xml_data_2.as_dte_data_l2(), DteDataL2( emisor_rut=Rut('60910000-1'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=2336600, fecha_emision_date=date(2019, 8, 8), receptor_rut=Rut('76555835-2'), monto_total=10642, emisor_razon_social='Universidad de Chile', receptor_razon_social='FYNPAL SPA', fecha_vencimiento_date=date(2019, 8, 8), firma_documento_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 8, 9, 9, 41, 9), tz=DteXmlData.DATETIME_FIELDS_TZ), signature_value=self.dte_2_xml_signature_value, signature_x509_cert_der=self.dte_2_xml_cert_der, emisor_giro= 'Corporación Educacional y Servicios Profesionales', emisor_email=None, receptor_email=None, ))
def test_validate_dt_tz_tzinfo_zone_attribute_check(self) -> None: # Time zone: UTC. Source: Pytz: tzinfo_utc_pytz = TZ_UTC dt_with_tzinfo_utc_pytz = convert_naive_dt_to_tz_aware( datetime.datetime(2021, 1, 6, 15, 21), tzinfo_utc_pytz, ) # Time zone: UTC. Source: Python Standard Library: tzinfo_utc_stdlib = datetime.timezone.utc dt_with_tzinfo_utc_stdlib = datetime.datetime.fromisoformat( '2021-01-06T15:04+00:00') # Time zone: Not UTC. Source: Pytz: tzinfo_not_utc_pytz = _TZ_CL_SANTIAGO dt_with_tzinfo_not_utc_pytz = convert_naive_dt_to_tz_aware( datetime.datetime(2021, 1, 6, 15, 21), tzinfo_not_utc_pytz, ) # Time zone: Not UTC. Source: Python Standard Library: tzinfo_not_utc_stdlib = datetime.timezone( datetime.timedelta(days=-1, seconds=75600)) dt_with_tzinfo_not_utc_stdlib = datetime.datetime.fromisoformat( '2021-01-06T15:04-03:00') # Test datetimes with UTC time zone: expected_error_message = re.compile( r"^Object datetime.timezone.utc must have 'zone' attribute.$") with self.assertRaisesRegex(AssertionError, expected_error_message): validate_dt_tz(dt_with_tzinfo_utc_pytz, tzinfo_utc_stdlib) with self.assertRaisesRegex(AssertionError, expected_error_message): validate_dt_tz(dt_with_tzinfo_utc_stdlib, tzinfo_utc_pytz) # Test datetimes with non-UTC time zone: expected_error_message = re.compile( r"^Object" r" datetime.timezone\(datetime.timedelta\(days=-1, seconds=75600\)\)" r" must have 'zone' attribute.$") with self.assertRaisesRegex(AssertionError, expected_error_message): validate_dt_tz(dt_with_tzinfo_not_utc_pytz, tzinfo_not_utc_stdlib) # type: ignore with self.assertRaisesRegex(AssertionError, expected_error_message): validate_dt_tz(dt_with_tzinfo_not_utc_stdlib, tzinfo_not_utc_pytz)
def test_validate_datetime_tz(self) -> None: self._set_obj_1() obj = self.obj_1 # Test TZ-awareness: expected_validation_errors = [ { 'loc': ('fecha_firma_dt', ), 'msg': 'Value must be a timezone-aware datetime object.', 'type': 'value_error', }, ] with self.assertRaises(pydantic.ValidationError) as assert_raises_cm: dataclasses.replace( obj, fecha_firma_dt=datetime(2019, 4, 5, 12, 57, 32), ) validation_errors = assert_raises_cm.exception.errors() self.assertEqual(len(validation_errors), len(expected_validation_errors)) for expected_validation_error in expected_validation_errors: self.assertIn(expected_validation_error, validation_errors) # Test TZ-value: expected_validation_errors = [ { 'loc': ('fecha_firma_dt', ), 'msg': '(' '''"Timezone of datetime value must be 'America/Santiago'.",''' ' datetime.datetime(2019, 4, 5, 12, 57, 32, tzinfo=<UTC>)' ')', 'type': 'value_error', }, ] with self.assertRaises(pydantic.ValidationError) as assert_raises_cm: dataclasses.replace( obj, fecha_firma_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=tz_utils.TZ_UTC, ), ) validation_errors = assert_raises_cm.exception.errors() self.assertEqual(len(validation_errors), len(expected_validation_errors)) for expected_validation_error in expected_validation_errors: self.assertIn(expected_validation_error, validation_errors)
def validate_datetime(cls, v: object) -> object: if isinstance(v, str): v = datetime.fromisoformat(v) if isinstance(v, datetime): v = tz_utils.convert_naive_dt_to_tz_aware( dt=v, tz=data_models_aec.AecXml.DATETIME_FIELDS_TZ, ) return v
def postprocess(self, data: dict) -> dict: # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13' data['fecha_recepcion_dt'] = tz_utils.convert_naive_dt_to_tz_aware( dt=data['fecha_recepcion_dt'], tz=self.FIELD_FECHA_RECEPCION_DT_TZ) # >>> data['fecha_recepcion_dt'].isoformat() # '2018-10-23T01:54:13-03:00' # >>> data['fecha_recepcion_dt'].astimezone(pytz.UTC).isoformat() # '2018-10-23T04:54:13+00:00' if data['fecha_acuse_dt']: data['fecha_acuse_dt'] = tz_utils.convert_naive_dt_to_tz_aware( dt=data['fecha_acuse_dt'], tz=self.FIELD_FECHA_ACUSE_DT_TZ) # note: to express this value in another timezone (but the value does not change), do # `dt_obj.astimezone(pytz.timezone('some timezone'))` # Remove leading and trailing whitespace. data['emisor_razon_social'] = data['emisor_razon_social'].strip() return data
def _set_obj_1(self) -> None: obj_dte_natural_key = DteNaturalKey( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, ) obj = CesionAltNaturalKey( dte_key=obj_dte_natural_key, cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57), tz=CesionAltNaturalKey.DATETIME_FIELDS_TZ, ), ) self.assertIsInstance(obj, CesionAltNaturalKey) self.obj_1_dte_natural_key = obj_dte_natural_key self.obj_1 = obj
def _set_obj_1(self) -> None: obj = CesionAecXml( dte=DteDataL1( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, fecha_emision_date=date(2019, 4, 1), receptor_rut=Rut('96790240-3'), monto_total=2996301, ), seq=1, cedente_rut=Rut('76354771-K'), cesionario_rut=Rut('76389992-6'), monto_cesion=2996301, fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 1, 10, 22, 2), tz=CesionAecXml.DATETIME_FIELDS_TZ, ), fecha_ultimo_vencimiento=date(2019, 5, 1), cedente_razon_social= 'SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIMITADA', cedente_direccion='MERCED 753 16 ARBOLEDA DE QUIILOTA', cedente_email='*****@*****.**', cedente_persona_autorizada_rut=Rut('76354771-K'), cedente_persona_autorizada_nombre= 'SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIM', cesionario_razon_social='ST CAPITAL S.A.', cesionario_direccion='Isidora Goyenechea 2939 Oficina 602', cesionario_email='*****@*****.**', dte_deudor_email=None, cedente_declaracion_jurada= ('Se declara bajo juramento que SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA ' 'LIMITADA, RUT 76354771-K ha puesto a disposición del cesionario ST ' 'CAPITAL S.A., RUT 76389992-6, el o los documentos donde constan los ' 'recibos de las mercaderías entregadas o servicios prestados, entregados ' 'por parte del deudor de la factura MINERA LOS PELAMBRES, RUT 96790240-3, ' 'deacuerdo a lo establecido en la Ley N°19.983.'), ) self.assertIsInstance(obj, CesionAecXml) self.obj_1 = obj
def test_as_cesion_l2(self) -> None: self._set_obj_1() obj = self.obj_1 expected_output = CesionL2( dte_key=DteNaturalKey( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, ), seq=1, cedente_rut=Rut('76354771-K'), cesionario_rut=Rut('76389992-6'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 1, 10, 22, 2), tz=CesionL2.DATETIME_FIELDS_TZ, ), monto_cedido=2996301, dte_receptor_rut=Rut('96790240-3'), dte_fecha_emision=date(2019, 4, 1), dte_monto_total=2996301, fecha_ultimo_vencimiento=date(2019, 5, 1), cedente_razon_social= 'SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIMITADA', cedente_email='*****@*****.**', cesionario_razon_social='ST CAPITAL S.A.', cesionario_email='*****@*****.**', dte_deudor_email=None, cedente_declaracion_jurada= ('Se declara bajo juramento que SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA ' 'LIMITADA, RUT 76354771-K ha puesto a disposición del cesionario ST ' 'CAPITAL S.A., RUT 76389992-6, el o los documentos donde constan los ' 'recibos de las mercaderías entregadas o servicios prestados, entregados ' 'por parte del deudor de la factura MINERA LOS PELAMBRES, RUT 96790240-3, ' 'deacuerdo a lo establecido en la Ley N°19.983.'), ) obj_cesion_l2 = obj.as_cesion_l2() self.assertEqual(obj_cesion_l2, expected_output) self.assertEqual(obj_cesion_l2.natural_key, obj.natural_key) self.assertEqual(obj_cesion_l2.alt_natural_key, obj.alt_natural_key) self.assertEqual(obj_cesion_l2.dte_key, obj.dte.natural_key)
def test_as_dict(self) -> None: self.assertDictEqual( self.dte_l2_1.as_dict(), dict( emisor_rut=Rut('76354771-K'), tipo_dte=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.dte_1_xml_signature_value, signature_x509_cert_der=self.dte_1_xml_cert_der, emisor_giro='Ingenieria y Construccion', emisor_email='*****@*****.**', receptor_email=None, ))
def _set_obj_2(self) -> None: obj = CesionAecXml( dte=DteDataL1( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, fecha_emision_date=date(2019, 4, 1), receptor_rut=Rut('96790240-3'), monto_total=2996301, ), seq=2, cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), monto_cesion=2996301, fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=CesionAecXml.DATETIME_FIELDS_TZ, ), fecha_ultimo_vencimiento=date(2019, 5, 1), cedente_razon_social='ST CAPITAL S.A.', cedente_direccion='Isidora Goyenechea 2939 Oficina 602', cedente_email='*****@*****.**', cedente_persona_autorizada_rut=Rut('16360379-9'), cedente_persona_autorizada_nombre='ANDRES PRATS VIAL', cesionario_razon_social= 'Fondo de Inversión Privado Deuda y Facturas', cesionario_direccion='Arrayan 2750 Oficina 703 Providencia', cesionario_email='*****@*****.**', dte_deudor_email=None, cedente_declaracion_jurada= ('Se declara bajo juramento que ST CAPITAL S.A., RUT 76389992-6 ha puesto ' 'a disposicion del cesionario Fondo de Inversión Privado Deuda y Facturas, ' 'RUT 76598556-0, el documento validamente emitido al deudor MINERA LOS ' 'PELAMBRES, RUT 96790240-3.'), ) self.assertIsInstance(obj, CesionAecXml) self.obj_2 = obj
def test_as_cesion_l1(self): self._set_obj_1() obj = self.obj_1 expected_output = CesionL1( dte_key=DteNaturalKey( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, ), seq=32, cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=CesionL1.DATETIME_FIELDS_TZ, ), monto_cedido=2996301, fecha_ultimo_vencimiento=date(2019, 5, 1), dte_fecha_emision=date(2019, 4, 1), dte_receptor_rut=Rut('96790240-3'), dte_monto_total=2996301, ) self.assertEqual(obj.as_cesion_l1(), expected_output)
def setUp(self) -> None: super().setUp() self.valid_kwargs = dict( dte_vendedor_rut=Rut('51532520-4'), dte_deudor_rut=Rut('75320502-0'), dte_tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, dte_folio=3608460, dte_fecha_emision=date(2019, 2, 11), dte_monto_total=256357, cedente_rut=Rut('51532520-4'), cedente_razon_social='MI CAMPITO SA', cedente_email='*****@*****.**', cesionario_rut=Rut('96667560-8'), cesionario_razon_social='POBRES SERVICIOS FINANCIEROS S.A.', cesionario_emails='[email protected],[email protected]', deudor_email=None, fecha_cesion_dt=convert_naive_dt_to_tz_aware( datetime(2019, 3, 7, 13, 32), tz=SII_OFFICIAL_TZ), fecha_cesion=date(2019, 3, 7), monto_cedido=256357, fecha_ultimo_vencimiento=date(2019, 4, 12), estado='Cesion Vigente', )
def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData: """ Parse data from a DTE XML doc. .. warning:: It is assumed that ``xml_doc`` is an ``{http://www.sii.cl/SiiDte}/DTE`` XML element. :raises ValueError: :raises TypeError: :raises NotImplementedError: """ # TODO: separate the XML parsing stage from the deserialization stage, which could be # performed by XML-agnostic code (perhaps using Marshmallow or data clacases?). # See :class:`cl_sii.rcv.parse_csv.RcvVentaCsvRowSchema`. if not isinstance(xml_doc, (XmlElement, XmlElementTree)): raise TypeError("'xml_doc' must be an 'XmlElement'.") xml_em = xml_doc ########################################################################### # XML elements finding ########################################################################### # Schema requires one, and only one, of these: # a) 'Documento' # b) 'Liquidacion' # c) 'Exportaciones' documento_em = xml_em.find( 'sii-dte:Documento', # "Informacion Tributaria del DTE" namespaces=DTE_XMLNS_MAP) liquidacion_em = xml_em.find( 'sii-dte:Liquidacion', # "Informacion Tributaria de Liquidaciones" namespaces=DTE_XMLNS_MAP) exportaciones_em = xml_em.find( 'sii-dte:Exportaciones', # "Informacion Tributaria de exportaciones" namespaces=DTE_XMLNS_MAP) signature_em = xml_em.find( 'ds:Signature', # "Firma Digital sobre Documento" namespaces=xml_utils.XML_DSIG_NS_MAP) if liquidacion_em is not None or exportaciones_em is not None: raise NotImplementedError("XML element 'Documento' is the only one supported.") if documento_em is None: raise ValueError("Top level XML element 'Document' is required.") # This value seems to be worthless (only useful for internal references in the XML doc). # e.g. 'MiPE76354771-13419', 'MiPE76399752-6048' # documento_em_id = documento_em.attrib['ID'] # 'Documento' # Excluded elements (optional according to the XML schema but the SII may require some of these # depending on 'tipo_dte' and other criteria): # - 'Detalle': (occurrences: 0..60) # "Detalle de Itemes del Documento" # - 'SubTotInfo': (occurrences: 0..20) # "Subtotales Informativos" # - 'DscRcgGlobal': (occurrences: 0..20) # "Descuentos y/o Recargos que afectan al total del Documento" # - 'Referencia': (occurrences: 0..40) # "Identificacion de otros documentos Referenciados por Documento" # - 'Comisiones': (occurrences: 0..20) # "Comisiones y otros cargos es obligatoria para Liquidaciones Factura" encabezado_em = documento_em.find( 'sii-dte:Encabezado', # "Identificacion y Totales del Documento" namespaces=DTE_XMLNS_MAP) # note: excluded because currently it is not useful. # ted_em = documento_em.find( # 'sii-dte:TED', # "Timbre Electronico de DTE" # namespaces=DTE_XMLNS_MAP) tmst_firma_em = documento_em.find( 'sii-dte:TmstFirma', # "Fecha y Hora en que se Firmo Digitalmente el Documento" namespaces=DTE_XMLNS_MAP) # 'Documento.Encabezado' # Excluded elements (optional according to the XML schema but the SII may require some of these # depending on 'tipo_dte' and other criteria): # - 'RUTMandante': # "RUT a Cuenta de Quien se Emite el DTE" # - 'RUTSolicita': # "RUT que solicita el DTE en Venta a Publico" # - 'Transporte': # "Informacion de Transporte de Mercaderias" # - 'OtraMoneda': # "Otra Moneda" id_doc_em = encabezado_em.find( 'sii-dte:IdDoc', # "Identificacion del DTE" namespaces=DTE_XMLNS_MAP) emisor_em = encabezado_em.find( 'sii-dte:Emisor', # "Datos del Emisor" namespaces=DTE_XMLNS_MAP) receptor_em = encabezado_em.find( 'sii-dte:Receptor', # "Datos del Receptor" namespaces=DTE_XMLNS_MAP) totales_em = encabezado_em.find( 'sii-dte:Totales', # "Montos Totales del DTE" namespaces=DTE_XMLNS_MAP) # 'Documento.Encabezado.IdDoc' # Excluded elements (optional according to the XML schema but the SII may require some of these # depending on 'tipo_dte' and other criteria): # - 'IndNoRebaja': # "Nota de Credito sin Derecho a Descontar Debito" # - 'TipoDespacho': # "Indica Modo de Despacho de los Bienes que Acompanan al DTE" # - 'IndTraslado': # "Incluido en Guias de Despacho para Especifiicar el Tipo de Traslado de Productos" # - 'TpoImpresion': # "Tipo de impresión N (Normal) o T (Ticket)" # - 'IndServicio': # "Indica si Transaccion Corresponde a la Prestacion de un Servicio" # - 'MntBruto': # "Indica el Uso de Montos Brutos en Detalle" # - 'TpoTranCompra': # "Tipo de Transacción para el comprador" # - 'TpoTranVenta': # "Tipo de Transacción para el vendedor" # - 'FmaPago': # "Forma de Pago del DTE" # - 'FmaPagExp': # "Forma de Pago Exportación Tabla Formas de Pago de Aduanas" # - 'FchCancel': # "Fecha de Cancelacion del DTE" # - 'MntCancel': # "Monto Cancelado al emitirse el documento" # - 'SaldoInsol': # "Saldo Insoluto al emitirse el documento" # - 'MntPagos': (occurrences: 0..30) # "Tabla de Montos de Pago" # - 'PeriodoDesde': # "Periodo de Facturacion - Desde" # - 'PeriodoHasta': # "Periodo Facturacion - Hasta" # - 'MedioPago': # "Medio de Pago" # - 'TpoCtaPago': # "Tipo Cuenta de Pago" # - 'NumCtaPago': # "Número de la cuenta del pago" # - 'BcoPago': # "Banco donde se realiza el pago" # - 'TermPagoCdg': # "Codigo del Termino de Pago Acordado" # - 'TermPagoGlosa': # "Términos del Pago - glosa" # - 'TermPagoDias': # "Dias de Acuerdo al Codigo de Termino de Pago" # (required): tipo_dte_em = id_doc_em.find( 'sii-dte:TipoDTE', # "Tipo de DTE" namespaces=DTE_XMLNS_MAP) folio_em = id_doc_em.find( 'sii-dte:Folio', # "Folio del Documento Electronico" namespaces=DTE_XMLNS_MAP) fecha_emision_em = id_doc_em.find( 'sii-dte:FchEmis', # "Fecha Emision Contable del DTE" namespaces=DTE_XMLNS_MAP) # (optional): fecha_vencimiento_em = id_doc_em.find( 'sii-dte:FchVenc', # "Fecha de Vencimiento del Pago" namespaces=DTE_XMLNS_MAP) # 'Documento.Encabezado.Emisor' # Excluded elements (optional according to the XML schema but the SII may require some of these # depending on 'tipo_dte' and other criteria): # - 'Telefono': (occurrences: 0..2) # "Telefono Emisor" # - 'Acteco': (occurrences: 0..4) # "Codigo de Actividad Economica del Emisor Relevante para el DTE" # - 'GuiaExport': # "Emisor de una Guía de despacho para Exportación" # - 'Sucursal': # "Sucursal que Emite el DTE" # - 'CdgSIISucur': # "Codigo de Sucursal Entregado por el SII" # - 'DirOrigen': # "Direccion de Origen" # - 'CmnaOrigen': # "Comuna de Origen" # - 'CiudadOrigen': # "Ciudad de Origen" # - 'CdgVendedor': # "Codigo del Vendedor" # - 'IdAdicEmisor': # "Identificador Adicional del Emisor" # (required): emisor_rut_em = emisor_em.find( 'sii-dte:RUTEmisor', # "RUT del Emisor del DTE" namespaces=DTE_XMLNS_MAP) emisor_razon_social_em = emisor_em.find( 'sii-dte:RznSoc', # "Nombre o Razon Social del Emisor" namespaces=DTE_XMLNS_MAP) emisor_giro_em = emisor_em.find( 'sii-dte:GiroEmis', # "Giro Comercial del Emisor Relevante para el DTE" namespaces=DTE_XMLNS_MAP) # (optional): emisor_email_em = emisor_em.find( 'sii-dte:CorreoEmisor', # "Correo Elect. de contacto en empresa del receptor" (wrong!) namespaces=DTE_XMLNS_MAP) # 'Documento.Encabezado.Receptor' # Excluded elements (optional according to the XML schema but the SII may require some of these # depending on 'tipo_dte' and other criteria): # - 'CdgIntRecep': # "Codigo Interno del Receptor" # - 'Extranjero': # "Receptor Extranjero" # - 'GiroRecep': # "Giro Comercial del Receptor" # - 'Contacto': # "Telefono o E-mail de Contacto del Receptor" # - 'CorreoRecep': # "Correo Elect. de contacto en empresa del receptor" # - 'DirRecep': # "Direccion en la Cual se Envian los Productos o se Prestan los Servicios" # - 'CmnaRecep': # "Comuna de Recepcion" # - 'CiudadRecep': # "Ciudad de Recepcion" # - 'DirPostal': # "Direccion Postal" # - 'CmnaPostal': # "Comuna Postal" # - 'CiudadPostal': # "Ciudad Postal" # (required): receptor_rut_em = receptor_em.find( 'sii-dte:RUTRecep', # "RUT del Receptor del DTE" namespaces=DTE_XMLNS_MAP) receptor_razon_social_em = receptor_em.find( 'sii-dte:RznSocRecep', # "Nombre o Razon Social del Receptor" namespaces=DTE_XMLNS_MAP) # (optional): receptor_email_em = emisor_em.find( 'sii-dte:CorreoRecep', # "Correo Elect. de contacto en empresa del receptor" namespaces=DTE_XMLNS_MAP) # 'Documento.Encabezado.Totales' # Excluded elements (optional according to the XML schema but the SII may require some of these # depending on 'tipo_dte' and other criteria): # - 'MntNeto': # "Monto Neto del DTE" # - 'MntExe': # "Monto Exento del DTE" # - 'MntBase': # "Monto Base Faenamiento Carne" (???) # - 'MntMargenCom': # "Monto Base de Márgenes de Comercialización. Monto informado" # - 'TasaIVA': # "Tasa de IVA" (percentage) # - 'IVA': # "Monto de IVA del DTE" # - 'IVAProp': # "Monto del IVA propio" # - 'IVATerc': # "Monto del IVA de Terceros" # - 'ImptoReten': (occurrences: 0..20) # "Impuestos y Retenciones Adicionales" # - 'IVANoRet': # "IVA No Retenido" # - 'CredEC': # "Credito Especial Empresas Constructoras" # - 'GrntDep': # "Garantia por Deposito de Envases o Embalajes" # - 'Comisiones': # "Comisiones y otros cargos es obligatoria para Liquidaciones Factura" # - 'MontoNF': # "Monto No Facturable - Corresponde a Bienes o Servicios Facturados Previamente" # - 'MontoPeriodo': # "Total de Ventas o Servicios del Periodo" # - 'SaldoAnterior': # "Saldo Anterior - Puede ser Negativo o Positivo" # - 'VlrPagar': # "Valor a Pagar Total del documento" monto_total_em = totales_em.find( 'sii-dte:MntTotal', # "Monto Total del DTE" namespaces=DTE_XMLNS_MAP) # 'Signature' # signature_signed_info_em = signature_em.find( # 'ds:SignedInfo', # "Descripcion de la Informacion Firmada y del Metodo de Firma" # namespaces=xml_utils.XML_DSIG_NS_MAP) # signature_signed_info_canonicalization_method_em = signature_signed_info_em.find( # 'ds:CanonicalizationMethod', # "Algoritmo de Canonicalizacion" # namespaces=xml_utils.XML_DSIG_NS_MAP) # signature_signed_info_signature_method_em = signature_signed_info_em.find( # 'ds:SignatureMethod', # "Algoritmo de Firma" # namespaces=xml_utils.XML_DSIG_NS_MAP) # signature_signed_info_reference_em = signature_signed_info_em.find( # 'ds:Reference', # "Referencia a Elemento Firmado" # namespaces=xml_utils.XML_DSIG_NS_MAP) signature_signature_value_em = signature_em.find( 'ds:SignatureValue', # "Valor de la Firma Digital" namespaces=xml_utils.XML_DSIG_NS_MAP) signature_key_info_em = signature_em.find( 'ds:KeyInfo', # "Informacion de Claves Publicas y Certificado" namespaces=xml_utils.XML_DSIG_NS_MAP) # signature_key_info_key_value_em = signature_key_info_em.find( # 'ds:KeyValue', # namespaces=xml_utils.XML_DSIG_NS_MAP) signature_key_info_x509_data_em = signature_key_info_em.find( 'ds:X509Data', # "Informacion del Certificado Publico" namespaces=xml_utils.XML_DSIG_NS_MAP) signature_key_info_x509_cert_em = signature_key_info_x509_data_em.find( 'ds:X509Certificate', # "Certificado Publico" namespaces=xml_utils.XML_DSIG_NS_MAP) ########################################################################### # values parsing ########################################################################### 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( _text_strip_or_raise(fecha_vencimiento_em)) 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(_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(_text_strip_or_raise(monto_total_em)) tmst_firma_value = tz_utils.convert_naive_dt_to_tz_aware( dt=datetime.fromisoformat(_text_strip_or_raise(tmst_firma_em)), tz=data_models.DteXmlData.DATETIME_FIELDS_TZ) signature_signature_value = encoding_utils.decode_base64_strict( _text_strip_or_raise(signature_signature_value_em)) signature_key_info_x509_cert_der = encoding_utils.decode_base64_strict( _text_strip_or_raise(signature_key_info_x509_cert_em)) return data_models.DteXmlData( emisor_rut=emisor_rut_value, tipo_dte=tipo_dte_value, folio=folio_value, fecha_emision_date=fecha_emision_value, receptor_rut=receptor_rut_value, monto_total=monto_total_value, emisor_razon_social=emisor_razon_social_value, receptor_razon_social=receptor_razon_social_value, fecha_vencimiento_date=fecha_vencimiento_value, firma_documento_dt=tmst_firma_value, signature_value=signature_signature_value, signature_x509_cert_der=signature_key_info_x509_cert_der, emisor_giro=emisor_giro_value, emisor_email=emisor_email_value, receptor_email=receptor_email_value, )
def as_datetime(self) -> datetime: # note: timezone-aware return tz_utils.convert_naive_dt_to_tz_aware( datetime(self.year, self.month, day=1, hour=0, minute=0, second=0), SII_OFFICIAL_TZ)
def _set_obj_1(self) -> None: obj_dte_signature_value = encoding_utils.decode_base64_strict( read_test_file_bytes( 'test_data/sii-crypto/DTE--76354771-K--33--170-signature-value-base64.txt', ), ) obj_dte_signature_x509_cert_der = read_test_file_bytes( 'test_data/sii-crypto/DTE--76354771-K--33--170-cert.der', ) obj_dte = DteXmlData( emisor_rut=Rut('76354771-K'), tipo_dte=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=DteXmlData.DATETIME_FIELDS_TZ, ), signature_value=obj_dte_signature_value, signature_x509_cert_der=obj_dte_signature_x509_cert_der, emisor_giro='Ingenieria y Construccion', emisor_email='*****@*****.**', receptor_email=None, ) obj_cesion_1 = CesionAecXml( dte=DteDataL1( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, fecha_emision_date=date(2019, 4, 1), receptor_rut=Rut('96790240-3'), monto_total=2996301, ), seq=1, cedente_rut=Rut('76354771-K'), cesionario_rut=Rut('76389992-6'), monto_cesion=2996301, fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 1, 10, 22, 2), tz=CesionAecXml.DATETIME_FIELDS_TZ, ), fecha_ultimo_vencimiento=date(2019, 5, 1), cedente_razon_social= 'SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIMITADA', cedente_direccion='MERCED 753 16 ARBOLEDA DE QUIILOTA', cedente_email='*****@*****.**', cedente_persona_autorizada_rut=Rut('76354771-K'), cedente_persona_autorizada_nombre= 'SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA LIM', cesionario_razon_social='ST CAPITAL S.A.', cesionario_direccion='Isidora Goyenechea 2939 Oficina 602', cesionario_email='*****@*****.**', dte_deudor_email=None, cedente_declaracion_jurada= ('Se declara bajo juramento que SERVICIOS BONILLA Y LOPEZ Y COMPAÑIA ' 'LIMITADA, RUT 76354771-K ha puesto a disposición del cesionario ST ' 'CAPITAL S.A., RUT 76389992-6, el o los documentos donde constan los ' 'recibos de las mercaderías entregadas o servicios prestados, entregados ' 'por parte del deudor de la factura MINERA LOS PELAMBRES, RUT 96790240-3, ' 'deacuerdo a lo establecido en la Ley N°19.983.'), ) obj_cesion_2 = CesionAecXml( dte=DteDataL1( emisor_rut=Rut('76354771-K'), tipo_dte=TipoDteEnum.FACTURA_ELECTRONICA, folio=170, fecha_emision_date=date(2019, 4, 1), receptor_rut=Rut('96790240-3'), monto_total=2996301, ), seq=2, cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), monto_cesion=2996301, fecha_cesion_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=CesionAecXml.DATETIME_FIELDS_TZ, ), fecha_ultimo_vencimiento=date(2019, 5, 1), cedente_razon_social='ST CAPITAL S.A.', cedente_direccion='Isidora Goyenechea 2939 Oficina 602', cedente_email='*****@*****.**', cedente_persona_autorizada_rut=Rut('16360379-9'), cedente_persona_autorizada_nombre='ANDRES PRATS VIAL', cesionario_razon_social= 'Fondo de Inversión Privado Deuda y Facturas', cesionario_direccion='Arrayan 2750 Oficina 703 Providencia', cesionario_email='*****@*****.**', dte_deudor_email=None, cedente_declaracion_jurada= ('Se declara bajo juramento que ST CAPITAL S.A., RUT 76389992-6 ha puesto ' 'a disposicion del cesionario Fondo de Inversión Privado Deuda y Facturas, ' 'RUT 76598556-0, el documento validamente emitido al deudor MINERA LOS ' 'PELAMBRES, RUT 96790240-3.'), ) obj = AecXml( dte=obj_dte, cedente_rut=Rut('76389992-6'), cesionario_rut=Rut('76598556-0'), fecha_firma_dt=tz_utils.convert_naive_dt_to_tz_aware( dt=datetime(2019, 4, 5, 12, 57, 32), tz=AecXml.DATETIME_FIELDS_TZ, ), # signature_value=None, # signature_x509_cert_der=None, cesiones=[ obj_cesion_1, obj_cesion_2, ], contacto_nombre='ST Capital Servicios Financieros', contacto_telefono=None, contacto_email='*****@*****.**', ) self.assertIsInstance(obj, AecXml) self.obj_1 = obj self.obj_1_dte = obj_dte self.obj_1_cesion_1 = obj_cesion_1 self.obj_1_cesion_2 = obj_cesion_2