Source code for satcfdi.accounting.contabilidad

import os
from typing import Sequence

from satcfdi.zip import zip_create, ZipData, zip_file

from satcfdi.catalogs import select, catalog_code

from satcfdi.utils import iterate

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


[docs] 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}")
[docs] def output_file(file, folder, fiel=None, generate_pdf=False, zip_xml=False): if fiel: file.sign(fiel) output_file = os.path.join(folder, filename(file)) if zip_xml: zip_file(output_file[:-4] + '.zip', [ ZipData( filename(file), file.xml_bytes(xml_declaration=True) ) ]) else: file.xml_write( output_file, pretty_print=True, xml_declaration=True ) if generate_pdf: render.pdf_write(file, output_file[:-4] + ".pdf") else: try: os.remove(output_file[:-4] + ".pdf") except FileNotFoundError: pass return output_file
[docs] def generar_contabilidad( dp: DatePeriod, rfc_emisor: str, cuentas: 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, zip_xml=False ): validate_cuentas(cuentas) validate_polizas(polizas) calcular_saldos(cuentas, polizas) 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, zip_xml=zip_xml) cat = Catalogo( rfc=rfc_emisor, mes=str(dp.month).zfill(2), anio=dp.year, ctas=[ Ctas( cod_agrup=v["CodAgrup"], num_cta=k, desc=v["Desc"], nivel=v["Nivel"], natur=v["Natur"], sub_cta_de=v['SubCtaDe'], ) for k, v in cuentas.items() ] ) output_file(cat, folder, fiel, zip_xml=zip_xml) 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 cuentas.items() if v["SaldoIni"] or v["Debe"] or v["Haber"] or v["SaldoFin"]], ) output_file(ban, folder, fiel, zip_xml=zip_xml) 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 cuentas.items() if k in aux_detalles ] ) output_file(aux, folder, fiel, generate_pdf=generate_pdf, zip_xml=zip_xml) 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, zip_xml=zip_xml) imprimir_contablidad( catalogo_cuentas=cat, balanza_comprobacion=ban, archivo_excel=os.path.join(folder, filename(ban)[:-4] + ".xlsx") ) validate_saldos(cuentas)
[docs] 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=p["Concepto"] + " " + t["Concepto"], debe=t["Debe"], haber=t["Haber"], ) ) return cta_polizas
[docs] def group_aux_folios(polizas): for p in polizas: compr_nal = [] compr_nal_otr = [] compr_ext = [] for t in p["Transaccion"]: if c := t.get('CompNal'): for c in iterate(c): if c not in compr_nal: compr_nal.append(c) if c := t.get('CompNalOtr'): for c in iterate(c): if c not in compr_nal_otr: compr_nal_otr.append(c) if c := t.get('CompExt'): for c in iterate(c): if c not in compr_ext: compr_ext.append(c) yield DetAuxFol( num_un_iden_pol=p["NumUnIdenPol"], fecha=p["Fecha"], compr_nal=compr_nal, compr_nal_otr=compr_nal_otr, compr_ext=compr_ext, )
[docs] def validate_cuentas(cuentas): # validar cuentas for k, v in cuentas.items(): assert k v['_Lowest'] = True assert v['Natur'] in ['A', 'D'] if v['SubCtaDe']: assert v['SubCtaDe'] in cuentas, f"Parent account {v['SubCtaDe']} not found for {k}" v['Nivel'] = cuentas[v['SubCtaDe']]['Nivel'] + 1 else: v['Nivel'] = 1 v['CodAgrup'] = catalog_code('Cb9f_c_CodAgrup', v['CodAgrup']) assert v['CodAgrup'].description, f"Unknown CodAgrup: {v['CodAgrup']}" for k, v in cuentas.items(): if v['SubCtaDe']: cuentas[v['SubCtaDe']]['_Lowest'] = False
[docs] def sign(cta): if cta['Natur'] == 'D': return 1 return -1
[docs] def validate_saldos(cuentas): totales = {} for k, v in cuentas.items(): sub_cta = v.get('SubCtaDe') totales.setdefault(sub_cta, 0) totales[sub_cta] += v['SaldoFin'] * sign(v) for k, v in totales.items(): if k: if v != cuentas[k]['SaldoFin'] * sign(cuentas[k]): raise ValueError(f"Error in {k}: {v} != {cuentas[k]['SaldoFin']}") else: assert v == 0
[docs] def validate_polizas(polizas): num_un = set() for p in polizas: u = p['NumUnIdenPol'] if u in num_un: raise ValueError(f"Repeated NumUnIdenPol: {u}") num_un.add(u)
[docs] def calcular_saldos(cuentas, polizas): max_level = 1 for c in cuentas.values(): # c['SaldoIni'] = 0 c['Debe'] = 0 c['Haber'] = 0 c['SaldoFin'] = 0 max_level = max(max_level, c['Nivel']) for p in polizas: for t in p["Transaccion"]: num_cta = t["NumCta"] cuenta = cuentas[num_cta] assert cuenta["_Lowest"], f"Account {num_cta} is not a lowest level account" cuenta["Debe"] += t["Debe"] cuenta["Haber"] += t["Haber"] # Fill Parents for level in range(max_level, 1, -1): for k, v in cuentas.items(): if v['Nivel'] == level: parent = v['SubCtaDe'] if parent: p_cuenta = cuentas[parent] p_cuenta['Debe'] += v['Debe'] p_cuenta['Haber'] += v['Haber'] # Fill SaldoFin for c in cuentas.values(): s = sign(c) c["SaldoFin"] += c["SaldoIni"] + c["Debe"] * s - c["Haber"] * s