Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CFDI v4 support #9

Merged
merged 3 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ docs/_autosummary
# Pyre type checker
.pyre/

# examples and cfdis to test
# examples and schemas to test
data

# from pycharm
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
[![PyPI Latest Release](https://img.shields.io/pypi/v/cfdibills.svg)](https://pypi.org/project/cfdibills/)
[![codecov](https://codecov.io/gh/peguerosdc/cfdibills/branch/main/graph/badge.svg?token=IE6CNFJJMQ)](https://codecov.io/gh/peguerosdc/cfdibills)

Utility to inspect and verify CFDI (Mexican invoice) versions 3.3 and 4.0
Utility to parse CFDI (Mexican invoice) versions 3.3 and 4.0 and validate their status against the SAT.

## Features

* Load a CFDI in XML format into a [pydantic](https://github.com/samuelcolvin/pydantic) object
* CFDIs are validated against the XSD schema, but a thorough check (i.e. conditional values) is not performed.
* Query the status of a CFDI via SAT's web service
* Only presence of required fields is validated, but this package doesn't perform a thorough validation of the CFDI
standard.
* **DOESN'T REQUIRE** additional dependencies to read the XML like libxml2-dev, libxslt-dev


Expand All @@ -23,12 +22,12 @@ pip install cfdibills

## Examples

You can load a verify a bill directly from its XML:
You can load and verify a bill directly from its XML:

````python
import cfdibills

cfdi = cfdibills.read_xml("path/to/invoice.xml")
cfdi = cfdibills.read_xml("path/to/bill.xml")
status = cfdibills.verify(cfdi)
````

Expand All @@ -37,9 +36,22 @@ Or you can verify it manually:
````python
import cfdibills

cfdibills.verify(uuid="folio fiscal", rfc_emisor="re", rfc_receptor="rr", total_facturado=150.00)
status = cfdibills.verify(uuid="folio fiscal", rfc_emisor="re", rfc_receptor="rr", total_facturado=150.00)
````

In both cases, `status` would look something like this:

````python
SATConsultaResponse(
codigo_estatus='S - Comprobante obtenido satisfactoriamente.',
es_cancelable='Cancelable con aceptación',
estado='Vigente',
estatus_cancelacion=None,
validacion_efos='200',
)
````


## Contributing

This repository uses [pre-commit](https://pre-commit.com/) to help developers perform almost the same validations as in
Expand Down
2 changes: 1 addition & 1 deletion cfdibills/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.0.a3
0.2.0.a4
26 changes: 16 additions & 10 deletions cfdibills/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
import pydantic
import xmltodict

from cfdibills.cfdis.cfdi33 import CFDI33
from cfdibills.errors import InvalidCFDIError, UnsupportedCFDIError
from cfdibills.schemas.cfdi33 import CFDI33
from cfdibills.schemas.cfdi40 import CFDI40

_name_pattern = re.compile(r"(.)([A-Z][a-z]+)")
_snake_pattern = re.compile(r"([a-z0-9])([A-Z])")
Expand All @@ -27,12 +28,19 @@ def _get_cfdi_with_version(candidate: dict) -> tuple[dict, str]:
return cfdi, version


def _parse_cfdi(cfdi: dict, version: str) -> CFDI33:
mapper = {"3.3": CFDI33}
def _xml_to_json(path: str, normalize: bool = True) -> dict:
with open(path, "rb") as f:
raw_xml = xmltodict.parse(f, dict_constructor=dict)
return normalize_dict_keys(raw_xml) if normalize else raw_xml


def _parse_cfdi(cfdi: dict, version: str) -> Union[CFDI33, CFDI40]:
mapper = {"3.3": CFDI33, "4.0": CFDI40}
if (parser := mapper.get(version, None)) is None:
raise UnsupportedCFDIError(f"Version '{version}' is not supported. It must be one of {mapper.keys()}.")
try:
parsed = parser.parse_obj(cfdi)
# Mypy doesn't know that the parser is also of type BaseModel, so we have to tell it to ignore this line
parsed = parser.parse_obj(cfdi) # type: ignore
except pydantic.ValidationError as e:
raise InvalidCFDIError(str(e)) from None
return parsed
Expand Down Expand Up @@ -103,7 +111,7 @@ def normalize_dict_keys(ugly_dict: dict) -> dict:
return result


def read_xml(path: str) -> CFDI33:
def read_xml(path: str) -> Union[CFDI33, CFDI40]:
"""
Reads a CFDI in a .xml and maps it to a pydantic object.

Expand All @@ -113,8 +121,8 @@ def read_xml(path: str) -> CFDI33:

Returns
-------
CFDI33
Pydantic version of the
Union[CFDI33, CFDI40]
Pydantic object of the CFDI

Raises
------
Expand All @@ -123,8 +131,6 @@ def read_xml(path: str) -> CFDI33:
UnsupportedCFDIError
If the CFDI version of the XML is not supported
"""
with open(path, "rb") as f:
raw_xml = xmltodict.parse(f, dict_constructor=dict)
normalized_xml = normalize_dict_keys(raw_xml)
normalized_xml = _xml_to_json(path)
cfdi, version = _get_cfdi_with_version(normalized_xml)
return _parse_cfdi(cfdi, version)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
CFDIs definition.
"""
from . import cfdi33, cfdi40
from .catalogs import *
from .cfdi33 import *
from .complementos import *
98 changes: 98 additions & 0 deletions cfdibills/cfdis/catalogs.py → cfdibills/schemas/catalogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,3 +650,101 @@ class TipoRelacion(str, Enum):
pagos_en_parcialidades = "08"
#: Factura Generada por Pagos Diferidos
pagos_diferidos = "09"


class RegimenFiscal(str, Enum):
"""
Catalog of "Regimen Fiscal".

http://www.sat.gob.mx/sitio_internet/cfd/catalogos/catCFDI.xsd
"""

r601 = "601"
r603 = "603"
r605 = "605"
r606 = "606"
r607 = "607"
r608 = "608"
r609 = "609"
r610 = "610"
r611 = "611"
r612 = "612"
r614 = "614"
r615 = "615"
r616 = "616"
r620 = "620"
r621 = "621"
r622 = "622"
r623 = "623"
r624 = "624"
r625 = "625"
r626 = "626"
r628 = "628"
r629 = "629"
r630 = "630"


class ObjetoImp(str, Enum):
"""
Catalog of "Objecto Imp".

http://www.sat.gob.mx/sitio_internet/cfd/catalogos/catCFDI.xsd
"""

o01 = "01"
o02 = "02"
o03 = "03"


class Periodicidad(str, Enum):
"""
Catalog of "Periodicidad".

http://www.sat.gob.mx/sitio_internet/cfd/catalogos/catCFDI.xsd
"""

p01 = "01"
p02 = "02"
p03 = "03"
p04 = "04"
p05 = "05"


class Meses(str, Enum):
"""
Catalog of "Meses".

http://www.sat.gob.mx/sitio_internet/cfd/catalogos/catCFDI.xsd
"""

m01 = "01"
m02 = "02"
m03 = "03"
m04 = "04"
m05 = "05"
m06 = "06"
m07 = "07"
m08 = "08"
m09 = "09"
m10 = "10"
m11 = "11"
m12 = "12"
m13 = "13"
m14 = "14"
m15 = "15"
m16 = "16"
m17 = "17"
m18 = "18"


class Exportacion(str, Enum):
"""
Catalog of "Exportacion".

http://www.sat.gob.mx/sitio_internet/cfd/catalogos/catCFDI.xsd
"""

e01 = "01"
e02 = "02"
e03 = "03"
e04 = "04"
Loading