def setUpClass(cls) -> None: super().setUpClass() cls.dte_bad_xml_1_xml_bytes = read_test_file_bytes( 'test_data/sii-dte/DTE--76354771-K--33--170.xml') cls.dte_bad_xml_2_xml_bytes = read_test_file_bytes( 'test_data/sii-dte/DTE--76399752-9--33--25568.xml') cls.dte_clean_xml_1_xml_bytes = read_test_file_bytes( '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( read_test_file_bytes('test_data/sii-crypto/DTE--76354771-K--33--170-cert.pem'))) cls.dte_clean_xml_1_cert_der = read_test_file_bytes( 'test_data/sii-crypto/DTE--76354771-K--33--170-cert.der') cls.dte_clean_xml_2_cert_pem_bytes = encoding_utils.clean_base64( crypto_utils.remove_pem_cert_header_footer( read_test_file_bytes('test_data/sii-crypto/DTE--76399752-9--33--25568-cert.pem'))) cls.dte_clean_xml_2_cert_der = read_test_file_bytes( 'test_data/sii-crypto/DTE--76399752-9--33--25568-cert.der') cls._TEST_DTE_1_SIGNATURE_VALUE = encoding_utils.decode_base64_strict( read_test_file_bytes( 'test_data/sii-crypto/DTE--76354771-K--33--170-signature-value-base64.txt')) cls._TEST_DTE_2_SIGNATURE_VALUE = encoding_utils.decode_base64_strict( read_test_file_bytes( 'test_data/sii-crypto/DTE--76399752-9--33--25568-signature-value-base64.txt'))
def setUpClass(cls) -> None: super().setUpClass() cls.dte_1_xml_signature_value = encoding_utils.decode_base64_strict( read_test_file_bytes( 'test_data/sii-crypto/DTE--76354771-K--33--170-signature-value-base64.txt' )) cls.dte_1_xml_cert_der = read_test_file_bytes( 'test_data/sii-crypto/DTE--76354771-K--33--170-cert.der') cls.dte_2_xml_signature_value = encoding_utils.decode_base64_strict( read_test_file_bytes( 'test_data/sii-crypto/DTE--60910000-1--33--2336600-signature-value-base64.txt' )) cls.dte_2_xml_cert_der = read_test_file_bytes( 'test_data/sii-crypto/DTE--60910000-1--33--2336600-cert.der')
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
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 validate_base64(cls, v: object) -> object: if isinstance(v, (str, bytes)): v = encoding_utils.decode_base64_strict(v) # Raises ValueError. return v