Cfdi Xml Transformation column format/csv


License
MIT
Install
pip install pycfdi-transform==0.1.11.0

Documentation

PyCfdi

PyCfdi Transform es un paquete de python que te permite convertir un Xml CFDI M茅xico a formato columnar.

Cfdi 3.3 con complementos:

  • Nomina 1.2
  • Pagos 1.0
  • ImpuestosLocales 1.0
  • TimbreFiscalDigital 1.1

SW sapien

Queremos compartir la experiencia que tenemos en Facturaci贸n Electr贸nica con la comunidad. Nuestro objetivo es facilitar la implementaci贸n y mantenimiento del Cfdi en M茅xico.

Build and Release status

Build Status

Release Status

Installation

Utiliza el package manager pip para instalar pycfdi-transform.

pip install pycfdi-transform

Usage

Para poder transformar un archivo XML a un objeto de tipo dictionary que contiene toda la informaci贸n del XML es necesario usar un Handler de CFDI de la siguiente manera

from  pycfdi_transform import CFDI33SAXHandler

path_xml = "./tests/Resources/cfdi33/cfdi33_01.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler() # Cfdi 3.3
cfdi_data = transformer.transform_from_file(path_xml)
print(cfdi_data) 

Contenido del xml mostrado

{
  "cfdi33": {
    "version": "3.3",
    "serie": "VF",
    "folio": "001002004",
    "fecha": "2020-04-30T22:36:13",
    "no_certificado": "30001000000400002434",
    "subtotal": "10.00",
    "descuento": "",
    "total": "11.60",
    "moneda": "MXN",
    "tipo_cambio": "",
    "tipo_comprobante": "I",
    "metodo_pago": "PPD",
    "forma_pago": "01",
    "condiciones_pago": "NET15",
    "lugar_expedicion": "84094",
    "emisor": {
      "rfc": "EKU9003173C9",
      "nombre": "ESCUELA KEMPER URGATE SA DE CV",
      "regimen_fiscal": "601"
    },
    "receptor": {
      "rfc": "XAXX010101000",
      "nombre": "PUBLICO EN GENERAL",
      "residencia_fiscal": "",
      "num_reg_id_trib": "",
      "uso_cfdi": "G03"
    },
    "conceptos": [],
    "impuestos": {
      "retenciones": [],
      "traslados": [
        {
          "impuesto": "002",
          "tipo_factor": "Tasa",
          "tasa_o_cuota": "0.160000",
          "importe": "1.60"
        }
      ],
      "total_impuestos_traslados": "1.60",
      "total_impuestos_retenidos": ""
    },
    "complementos": "TimbreFiscalDigital",
    "addendas": ""
  },
  "tfd11": [
    {
      "version": "1.1",
      "no_certificado_sat": "20001000000300022323",
      "uuid": "9D81C696-0401-4F85-B703-6E0D3AFD6056",
      "fecha_timbrado": "2020-05-02T00:36:50",
      "rfc_prov_cert": "AAA010101AAA",
      "sello_cfd": "SKndhzlakx2g1ykM73KJ8O0F02/ibJxmNqpEG6/878pu/8BUX/cgxWyh9O2EHhtITNlBZHD73Qgq9E7fuNOO/1xKuM9tgtzKrXqUmQ5bxhz2OfvynQ6Tmq6nzO+2FF6lyPmi2yxoeoGNtKjDIjnXNPAVYTS7n9V94dsciZaSmSGtT5LTIGTmA5QJQ5t3NzxL5+mkKqxc57W9PO9GRWybzsWnQwvG0XBoMU0n00qXMiVjGfCdzGcdku80qRtNTbL5OWPSgiR5Sc45X5V7Y8lUpaHk7a3zgQ/+haITyAlqux7bJtVGK4Zo78leiex3YbpcLH/gJ12jCqvPmFVJNAPZhw=="
    }
  ]
}

Una vez que tengamos la informaci贸n de la transformaci贸n del CFDI, entonces usaremos un Formatter para presentar esta informaci贸n en el formato columnar. Ejemplo

from pycfdi_transform.formatters.cfdi33.efisco_corp_cfdi33_formatter import EfiscoCorpCFDI33Formatter
formatter = CFDI33Formatter(cfdi_data)
if formatter.can_format(): # Verifica si puede formatear el objeto
  result_columns = formatter.dict_to_columns() # Obtiene la informaci贸n en formato columnar
  columns = formatter.get_columns_names() # Obtiene los headers de las columnas
  print(result_columns) # Contenido del xml Ej: ['3.3', 'A5', '5511', ...]
  print(columns) # Nombre de las columnas Ej: ['VERSION', 'SERIE', 'FOLIO', ...]
else:
  print(formatter.get_errors()) # Not tfd11 in data.

Complements

La configuraci贸n de complementos para la clase CFDI33SAXHandler se define a trav茅s de method chaining que se obtiene a trav茅s de contruir una nueva instancia del Handler. Por defecto se encuentra activado el complemento de TimbreFiscalDigital 1.1, por lo que solo tendremos que configurar complementos adicionales de los cuales queramos obtener informaci贸n.

NOTA: En caso de que declaremos un complemento y este no se encuentre en el XML no pasa nada, simplemente la llave del dictionary de python no se encontrar谩 en el resultado obtenido, as铆 entonces podemos tener una configuraci贸n avanzada para con el mismo c贸digo obtener multiples complementos seg煤n sea el caso.

NOTA2: Por temas de optimizaci贸n en CFDI globales, se expone un m茅todo adicional para obtener la informaci贸n de los conceptos.

Conceptos CFDI 3.3

En el caso de los conceptos del CFDI 3.3, por defecto estos campos no se obtienen ya que pocas veces se utilizan, sin embargo es posible obtener la informaci贸n de los conceptos de la siguiente manera

from  pycfdi_transform import CFDI33SAXHandler

path_xml = "./tests/Resources/cfdi33/cfdi33_01_utf8chars.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler().use_concepts_cfdi33() # Cfdi 3.3 con obtenci贸n de conceptos.
cfdi_data = transformer.transform_from_file(path_xml)
print(cfdi_data) 

As铆 entonces nuestro resultado en caso de contener conceptos ser铆a

{
  "cfdi33": {
    "version": "3.3",
    "serie": "VF",
    "folio": "001002004",
    ...
    "conceptos": [
      {
        "clave_prod_serv": "01010101",
        "no_identificacion": "prod眉ctoInventari贸",
        "cantidad": "1.0000",
        "clave_unidad": "3G",
        "unidad": "",
        "descripcion": "Detalle factura",
        "valor_unitario": "10.0000",
        "importe": "10.00",
        "descuento": ""
      }
    ],
  },
  "tfd11": [
    {
      "version": "1.1",
      "no_certificado_sat": "20001000000300022323",
      "uuid": "9D81C696-0401-4F85-B703-6E0D3AFD6056",
      ...
  ]
}

Nomina 1.2

Ejemplo para extraer adicionalmente la informaci贸n del complemento de nomina 1.2, entonces al crear nuestra instancia podemos usar la siguiente configuraci贸n

from  pycfdi_transform import CFDI33SAXHandler

path_xml = "./tests/Resources/nomina12/double_nomina01.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler().use_nomina12() # Cfdi 3.3 con soporte para nomina12
cfdi_data = transformer.transform_from_file(path_xml)
print(cfdi_data) 

As铆 entonces nuestro resultado en caso de contener un complemento de n贸mina ser铆a

{
  "cfdi33": {
    "version": "3.3",
    "serie": "VF",
    "folio": "001002004",
    ...
  },
  "tfd11": [
    {
      "version": "1.1",
      "no_certificado_sat": "20001000000300022323",
      "uuid": "9D81C696-0401-4F85-B703-6E0D3AFD6056",
      ...
  ]
  "nomina12": [
    {
      "version": "1.2",
      "tipo_nomina": "E",
      ...
  ]
}

Pagos 1.0

Para el caso del complemento de pagos 1.0, entonces al crear nuestra instancia podemos usar la siguiente configuraci贸n

from  pycfdi_transform import CFDI33SAXHandler

path_xml = "./tests/Resources/pagos10/pago10_01.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler().use_pagos10() # Cfdi 3.3 con soporte para pagos10
cfdi_data = transformer.transform_from_file(path_xml)
print(cfdi_data) 

As铆 entonces nuestro resultado en caso de contener un complemento de pagos ser铆a

{
  "cfdi33": {
    "version": "3.3",
    "serie": "VF",
    "folio": "001002004",
    ...
  },
  "tfd11": [
    {
      "version": "1.1",
      "no_certificado_sat": "20001000000300022323",
      "uuid": "9D81C696-0401-4F85-B703-6E0D3AFD6056",
      ...
  ]
  "pagos10": [
    {
      "version": "1.0",
      "pago": [
        {
          "fecha_pago": "2019-03-29T16:14:52",
          ...
     ]
  ]
}

Impuestos Locales 1.0

Para el caso del complemento de Impuestos Locales 1.0, entonces al crear nuestra instancia podemos usar la siguiente configuraci贸n

from  pycfdi_transform import CFDI33SAXHandler

path_xml = "./tests/Resources/implocal/cfdi33_implocal01.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler().use_implocal10() # Cfdi 3.3 con soporte para impuestos locales 1.0
cfdi_data = transformer.transform_from_file(path_xml)
print(cfdi_data) 

As铆 entonces nuestro resultado en caso de contener un complemento de impuestos locales ser铆a

{
  "cfdi33": {
    "version": "3.3",
    "serie": "VF",
    "folio": "001002004",
    ...
  },
  "tfd11": [
    {
      "version": "1.1",
      "no_certificado_sat": "20001000000300022323",
      "uuid": "9D81C696-0401-4F85-B703-6E0D3AFD6056",
      ...
  ]
  "implocal10": [
    {
      "total_traslados_impuestos_locales": "0.000000",
      "total_retenciones_impuestos_locales: "77.400000"
    }
  ]
}

Configurations

La clase CFDI33SAXHandler contiene par谩metros con los cuales se puede configurar el comportamiento al encontrar un valor opcional del XML que no se encuentra definido en el XML as铆 como si deber铆a utilizar numeros para cuando no se encuentre alg煤n atributo num茅rico opcional. Estas opciones de configuraci贸n son

  • empty_char: Valor para atributos opcionales en caso de no encontrarse en el XML.
  • safe_numerics: True o False para definir si utilizar el empty_char o no en los atributos de tipo n煤merico, por ejemplo Descuento.
  • schema_validator: Clase de tipo lxml.etree.XMLSchema para validar la estructura del XML antes de realizar la tranformaci贸n. Arroja excepcion de tipo lxml.etree.DocumentInvalid en caso de que no cumpla con la validaci贸n de XSD.

empty_char

Al definir un empty_char cuando se trate de un campo opcional entonces se mostrar谩 este valor en caso de no contener alg煤n valor en el XML. Ejemplo un XML que no contiene el atributo Serie. Este valor por defecto est谩 definido como un string vacio ''.

from  pycfdi_transform import CFDI33SAXHandler

path_xml = "./tests/Resources/cfdi33/cfdi33_01.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler(empty_char='#') # Cfdi 3.3
cfdi_data = transformer.transform_from_file(path_xml)
print(cfdi_data) 
{
  "cfdi33": {
    "version": "3.3",
    "serie": "#",
    "folio": "001002004",
...

safe_numerics

El atributo de la configuraci贸n safe_numerics tiene el objetivo de definir si se utiliza el empty_char en los atributos n煤mericos, por ejemplo el Descuento, TipoCambio, entre otros.

from  pycfdi_transform import CFDI33SAXHandler

path_xml = "./tests/Resources/cfdi33/cfdi33_01.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler(safe_numerics=True) # Cfdi 3.3
cfdi_data = transformer.transform_from_file(path_xml)
print(cfdi_data) 
{
  "cfdi33": {
    ...,
    "descuento": "0.00",
    "total": "11.60",
    "moneda": "MXN",
    "tipo_cambio": "1.00",
    "tipo_comprobante": "I"
    
...

schema_validator

Para poder hacer uso de las validaciones de XSD es necesario construir un objeto de tipo lxml.etree.XMLSchema para validar la estructura del XML antes de realizar la tranformaci贸n. Dentro de la librer铆a existe un helper que construye una instancia de esta clase con todos los XSD de complementos del SAT para CFDI 3.3.

Nota: Arroja excepcion de tipo lxml.etree.DocumentInvalid en caso de que no cumpla con la validaci贸n de XSD.

Ejemplo de uso

from pycfdi_transform import CFDI33SAXHandler, SchemaHelper
from lxml.etree import DocumentInvalid

xsd_validator = SchemaHelper.get_schema_validator_cfdi33() # Obtiene una instancia de clase lxml.etree.XMLSchema con los XSD del SAT.
path_xml = "./tests/Resources/cfdi33/cfdi33_01.xml"  #path xml que queremos transformar
transformer = CFDI33SAXHandler(schema_validator=xsd_validator, empty_char='#') # Cfdi 3.3 con validador de XSD.
try:
	cfdi_data = transformer.transform_from_file(path_xml)
	#CFDI v谩lido, resultado en la variable cfdi_data
	print(cfdi_data) 
except DocumentInvalid as ex:
	print(f"Document invalid, error: {ex}")

NOTA: Se puede construir el objeto lxml.etree.XMLSchema de manera custom a manera de solo soportar algunos complementos y no todos, la documentaci贸n sobre esta clase la encuentras en la p谩gina de lxml aqu铆.

Contributing

Pull requests son bienvenidos. Para cambios mayores, por favor abre un issue primero para poder discutir que deseas cambiar.

Asegurate de actualizar los tests de acuerdo a tus cambios.

Testing

python -m unittest discover

License

GNU