Skip to content

Commit

Permalink
Added Contabilidad functions
Browse files Browse the repository at this point in the history
  • Loading branch information
satcfdi committed Apr 9, 2024
1 parent b5f93a8 commit b20cbc2
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,4 @@ dmypy.json
/satcfdi/create/*/*_z.py
/docs/generated/
/satcfdi.svg
/tests/test_contabilidad_electronica/out/
195 changes: 195 additions & 0 deletions satcfdi/accounting/contabilidad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import os
from typing import Sequence

from satcfdi.create.contabilidad.AuxiliarCtas13 import AuxiliarCtas, Cuenta, DetalleAux
from satcfdi.create.contabilidad.BCE13 import Balanza
from satcfdi.create.contabilidad.PLZ13 import Polizas, CompNal, Poliza
from satcfdi.create.contabilidad.RepAux13 import RepAuxFol, DetAuxFol
from satcfdi.create.contabilidad.catalogocuentas13 import Catalogo, Ctas
from .contabilidad_print import imprimir_contablidad

from .. import render

from ..models import DatePeriod


def filename(file):
if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/BalanzaComprobacion}Balanza':
return file["RFC"] + str(file["Anio"]) + file["Mes"] + "B" + file["TipoEnvio"] + ".xml"
if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/CatalogoCuentas}Catalogo':
return file["RFC"] + str(file["Anio"]) + file["Mes"] + "CT.xml"
if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarCtas}AuxiliarCtas':
return file["RFC"] + str(file["Anio"]) + file["Mes"] + "XC.xml"
if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo}Polizas':
return file["RFC"] + str(file["Anio"]) + file["Mes"] + "PL.xml"
if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarFolios}RepAuxFol':
return file["RFC"] + str(file["Anio"]) + file["Mes"] + "XF.xml"
raise ValueError(f"Unknown file type: {file.tag}")


def output_file(file, folder, fiel=None, generate_pdf=False):
if fiel:
file.sign(fiel)

output_file = os.path.join(folder, filename(file))
file.xml_write(
output_file,
pretty_print=True,
xml_declaration=True
)
if generate_pdf:
# render.html_write(file, output_file[:-4] + ".html")
render.pdf_write(file, output_file[:-4] + ".pdf")
else:
# delete file
try:
os.remove(output_file[:-4] + ".pdf")
except FileNotFoundError:
pass

return output_file


def generar_contabilidad(
dp: DatePeriod,
rfc_emisor: str,
ctas: dict,
polizas: Sequence[Poliza],
tipo_envio='N',
fecha_mod_bal=None,
tipo_solicitud='',
numero_orden=None,
numero_tramite=None,
folder=None,
fiel=None,
generate_pdf=False):

plz = Polizas(
rfc=rfc_emisor,
mes=str(dp.month).zfill(2),
anio=dp.year,
tipo_solicitud=tipo_solicitud,
num_orden=numero_orden,
num_tramite=numero_tramite,
poliza=polizas
)
output_file(plz, folder, fiel, generate_pdf=generate_pdf)

cat = Catalogo(
rfc=rfc_emisor,
mes=str(dp.month).zfill(2),
anio=dp.year,
ctas=[
Ctas(
cod_agrup=v["CodAgrup"].split("_")[0],
num_cta=k,
desc=v["Desc"],
nivel=v["Nivel"],
natur=v["Natur"],
sub_cta_de=v['SubCtaDe'],
) for k, v in ctas.items()
]
)
output_file(cat, folder, fiel)

ban = Balanza(
rfc=rfc_emisor,
mes=str(dp.month).zfill(2),
anio=dp.year,
tipo_envio=tipo_envio,
fecha_mod_bal=fecha_mod_bal,
ctas=[{
"NumCta": k,
**v,
} for k, v in ctas.items() if v["SaldoIni"] or v["Debe"] or v["Haber"] or v["SaldoFin"]],
)
output_file(ban, folder, fiel)

aux_detalles = group_aux_cuentas(polizas)
aux = AuxiliarCtas(
rfc=rfc_emisor,
mes=str(dp.month).zfill(2),
anio=dp.year,
tipo_solicitud=tipo_solicitud,
num_orden=numero_orden,
num_tramite=numero_tramite,
cuenta=[
Cuenta(
num_cta=k,
des_cta=v["Desc"],
saldo_ini=v["SaldoIni"],
saldo_fin=v["SaldoFin"],
detalle_aux=aux_detalles[k]
) for k, v in ctas.items() if k in aux_detalles
]
)
output_file(aux, folder, fiel, generate_pdf=generate_pdf)

auxf = RepAuxFol(
rfc=rfc_emisor,
mes=str(dp.month).zfill(2),
anio=dp.year,
tipo_solicitud=tipo_solicitud,
num_orden=numero_orden,
num_tramite=numero_tramite,
det_aux_fol=list(group_aux_folios(polizas))
)
output_file(auxf, folder, fiel, generate_pdf=generate_pdf)

imprimir_contablidad(
catalogo_cuentas=cat,
balanza_comprobacion=ban,
archivo_excel=filename(ban)[:-4] + ".xlsx"
)

validate_saldos(ctas)


def group_aux_cuentas(polizas):
cta_polizas = {}
for p in polizas:
for t in p["Transaccion"]:
detalles = cta_polizas.setdefault(t["NumCta"], [])
detalles.append(
DetalleAux(
fecha=p["Fecha"],
num_un_iden_pol=p["NumUnIdenPol"],
concepto=t["Concepto"],
debe=t["Debe"],
haber=t["Haber"],
)
)
return cta_polizas


def group_aux_folios(polizas):
for p in polizas:
yield DetAuxFol(
num_un_iden_pol=p["NumUnIdenPol"],
fecha=p["Fecha"],
compr_nal=p.comp_nal,
)


def validate_saldos(cuentas):
total = 0
totales = {}
for k, v in cuentas.items():
if v['Nivel'] == 1:
if v['Natur'] == 'D':
total += v['SaldoFin']
else:
total -= v['SaldoFin']
else:
totales.setdefault(v['SubCtaDe'], 0)
if v['Natur'] == 'D':
totales[v['SubCtaDe']] += v['SaldoFin']
else:
totales[v['SubCtaDe']] -= v['SaldoFin']

assert total == 0
for k, v in totales.items():
if cuentas[k]['Natur'] == 'D':
assert v == cuentas[k]['SaldoFin']
else:
assert v == -cuentas[k]['SaldoFin']
51 changes: 51 additions & 0 deletions satcfdi/accounting/contabilidad_print.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import xlsxwriter
from satcfdi.accounting.process import excel_export
from satcfdi.cfdi import CFDI

EXCEL_COLUMNS = {
'NumCta': (12, False, lambda i: i['NumCta']),
'Desc': (62, False, lambda i: ' ' * (i['Nivel'] - 1) + i['Desc']),
'Natur': (5, False, lambda i: i['Natur']),

'SaldoIni': (12, False, lambda i: i.get('SaldoIni')),
'Debe': (12, False, lambda i: i.get('Debe')),
'Haber': (12, False, lambda i: i.get('Haber')),
'SaldoFin': (12, False, lambda i: i.get('SaldoFin')),

'CodAgrup': (12, False, lambda i: i['CodAgrup'].code),
'CodDesc': (120, False, lambda i: i['CodAgrup'].description),
}


def imprimir_contablidad(
catalogo_cuentas,
balanza_comprobacion,
archivo_excel
):
# ct = CFDI.from_file(catalogo_cuentas)
# bc = CFDI.from_file(balanza_comprobacion)
ct = catalogo_cuentas
bc = balanza_comprobacion

ctas = {
c['NumCta']: {
'Desc': c['Desc'],
'Natur': c['Natur'],
'CodAgrup': c['CodAgrup'],
'Nivel': c['Nivel'],
}
for c in ct['Ctas']
}
for r in bc['Ctas']:
ctv = ctas[r['NumCta']]
r.update(ctv)

workbook = xlsxwriter.Workbook(archivo_excel)
excel_export(
workbook=workbook,
name=bc['RFC'] + str(bc['Anio']) + str(bc['Mes']),
invoices=bc['Ctas'],
columns=EXCEL_COLUMNS,
row_height=1
)
workbook.close()
4 changes: 2 additions & 2 deletions satcfdi/accounting/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def num2col(n):
return name


def excel_export(workbook: xlsxwriter.workbook.Workbook, name, invoices, columns):
def excel_export(workbook: xlsxwriter.workbook.Workbook, name, invoices, columns, row_heigth=3):
worksheet = workbook.add_worksheet(name)

for n, (w, s, f) in enumerate(columns.values()):
Expand All @@ -275,7 +275,7 @@ def excel_export(workbook: xlsxwriter.workbook.Workbook, name, invoices, columns
number_format = workbook.add_format({'num_format': '0.00'})
number_format.set_align('top')

row_height = 20 * 3
row_height = 20 * row_heigth
r = 0
for r, i in enumerate(invoices):
worksheet.set_row_pixels(1 + r, row_height)
Expand Down
1 change: 1 addition & 0 deletions satcfdi/create/contabilidad/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file added tests/MOLE870717DRA202402BN.xlsx
Binary file not shown.
19 changes: 18 additions & 1 deletion tests/test_contabilidad.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import os

import pytest
from satcfdi.models import DatePeriod

from satcfdi import render
from satcfdi.accounting.contabilidad import generar_contabilidad
from satcfdi.cfdi import CFDI
from tests.constants import CFDI_FILES, CONTABILIDAD_FILES, SPEI_FILES
from tests.utils import verify_result, XElementPrettyPrinter
from tests.utils import verify_result, XElementPrettyPrinter, compare_directories

module = 'satcfdi'
current_dir = os.path.dirname(__file__)
Expand Down Expand Up @@ -44,3 +46,18 @@ def test_generate_spei_pdf(caplog, xml_file):
)

verify_invoice(cfdi, xml_file)


def test_generate_contabilidad():
generar_contabilidad(
dp=DatePeriod(2024, 2),
rfc_emisor="MOLE870717DRA",
ctas={},
polizas=[],
folder=os.path.join(current_dir, 'test_contabilidad_electronica/out'),
)

assert compare_directories(
os.path.join(current_dir, 'test_contabilidad_electronica/ref'),
os.path.join(current_dir, 'test_contabilidad_electronica/out')
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<BCE:Balanza xmlns:BCE="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/BalanzaComprobacion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.3" RFC="MOLE870717DRA" Mes="02" Anio="2024" TipoEnvio="N" xsi:schemaLocation="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/BalanzaComprobacion http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/BalanzaComprobacion/BalanzaComprobacion_1_3.xsd"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<catalogocuentas:Catalogo xmlns:catalogocuentas="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/CatalogoCuentas" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.3" RFC="MOLE870717DRA" Mes="02" Anio="2024" xsi:schemaLocation="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/CatalogoCuentas http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/CatalogoCuentas/CatalogoCuentas_1_3.xsd"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<PLZ:Polizas xmlns:PLZ="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.3" RFC="MOLE870717DRA" Mes="02" Anio="2024" TipoSolicitud="" xsi:schemaLocation="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo/PolizasPeriodo_1_3.xsd"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<AuxiliarCtas:AuxiliarCtas xmlns:AuxiliarCtas="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarCtas" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.3" RFC="MOLE870717DRA" Mes="02" Anio="2024" TipoSolicitud="" xsi:schemaLocation="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarCtas http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarCtas/AuxiliarCtas_1_3.xsd"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<RepAux:RepAuxFol xmlns:RepAux="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarFolios" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.3" RFC="MOLE870717DRA" Mes="02" Anio="2024" TipoSolicitud="" xsi:schemaLocation="http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarFolios http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarFolios/AuxiliarFolios_1_3.xsd"/>
30 changes: 30 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import filecmp
import inspect
import os
import uuid
Expand Down Expand Up @@ -97,6 +98,35 @@ class XElementPrettyPrinter(PrettyPrinter):
_dispatch[defaultdict.__repr__] = PrettyPrinter._pprint_dict


def compare_directories(dir1, dir2):
# Check if both paths are directories
if not (os.path.isdir(dir1) and os.path.isdir(dir2)):
return False

# Use filecmp to compare directories
dcmp = filecmp.dircmp(dir1, dir2)

# Check for common files that are different
if dcmp.diff_files:
print(f"Files that are different: {dcmp.diff_files} in {dir2}")
return False

# Check for files present in one directory but not in the other
if dcmp.left_only or dcmp.right_only:
print(f"Files present only in one directory: {dcmp.left_only + dcmp.right_only} in {dir2}")
return False

# Check for subdirectories that are not present in both directories
if dcmp.subdirs:
for subdir in dcmp.subdirs:
subdir1 = os.path.join(dir1, subdir)
subdir2 = os.path.join(dir2, subdir)
if not compare_directories(subdir1, subdir2):
return False

return True


if __name__ == "__main__":
import os

Expand Down

0 comments on commit b20cbc2

Please sign in to comment.