Passed
Push — main ( 92a304...476fed )
by Sat CFDI
05:29
created

satcfdi.accounting.contabilidad.group_aux_cuentas()   A

Complexity

Conditions 3

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.7085

Importance

Changes 0
Metric Value
cc 3
eloc 13
nop 1
dl 0
loc 15
ccs 4
cts 7
cp 0.5714
crap 3.7085
rs 9.75
c 0
b 0
f 0
1 1
import os
2 1
from typing import Sequence
3
4 1
from satcfdi.utils import iterate
5
6 1
from satcfdi.create.contabilidad.AuxiliarCtas13 import AuxiliarCtas, Cuenta, DetalleAux
7 1
from satcfdi.create.contabilidad.BCE13 import Balanza
8 1
from satcfdi.create.contabilidad.PLZ13 import Polizas, CompNal, Poliza
9 1
from satcfdi.create.contabilidad.RepAux13 import RepAuxFol, DetAuxFol
10 1
from satcfdi.create.contabilidad.catalogocuentas13 import Catalogo, Ctas
11 1
from .contabilidad_print import imprimir_contablidad
12
13 1
from .. import render
14
15 1
from ..models import DatePeriod
16
17
18 1
def filename(file):
19 1
    if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/BalanzaComprobacion}Balanza':
20 1
        return file["RFC"] + str(file["Anio"]) + file["Mes"] + "B" + file["TipoEnvio"] + ".xml"
21 1
    if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/CatalogoCuentas}Catalogo':
22 1
        return file["RFC"] + str(file["Anio"]) + file["Mes"] + "CT.xml"
23 1
    if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarCtas}AuxiliarCtas':
24 1
        return file["RFC"] + str(file["Anio"]) + file["Mes"] + "XC.xml"
25 1
    if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/PolizasPeriodo}Polizas':
26 1
        return file["RFC"] + str(file["Anio"]) + file["Mes"] + "PL.xml"
27 1
    if file.tag == '{http://www.sat.gob.mx/esquemas/ContabilidadE/1_3/AuxiliarFolios}RepAuxFol':
28 1
        return file["RFC"] + str(file["Anio"]) + file["Mes"] + "XF.xml"
29
    raise ValueError(f"Unknown file type: {file.tag}")
30
31
32 1
def output_file(file, folder, fiel=None, generate_pdf=False):
33 1
    if fiel:
34
        file.sign(fiel)
35
36 1
    output_file = os.path.join(folder, filename(file))
37 1
    file.xml_write(
38
        output_file,
39
        pretty_print=True,
40
        xml_declaration=True
41
    )
42 1
    if generate_pdf:
43
        # render.html_write(file, output_file[:-4] + ".html")
44
        render.pdf_write(file, output_file[:-4] + ".pdf")
45
    else:
46
        # delete file
47 1
        try:
48 1
            os.remove(output_file[:-4] + ".pdf")
49 1
        except FileNotFoundError:
50 1
            pass
51
52 1
    return output_file
53
54
55 1
def calcular_saldos(cuentas, polizas):
56 1
    max_level = 1
57 1
    for c in cuentas.values():
58
        # c['SaldoIni'] = 0
59 1
        c['Debe'] = 0
60 1
        c['Haber'] = 0
61 1
        c['SaldoFin'] = 0
62 1
        max_level = max(max_level, c['Nivel'])
63
64 1
    for p in polizas:
65 1
        for t in p["Transaccion"]:
66 1
            num_cta = t["NumCta"]
67 1
            cuenta = cuentas[num_cta]
68 1
            cuenta["Debe"] += t["Debe"]
69 1
            cuenta["Haber"] += t["Haber"]
70
71
    # Fill Parents
72 1
    for level in range(max_level, 1, -1):
73 1
        for k, v in cuentas.items():
74 1
            if v['Nivel'] == level:
75 1
                parent = v['SubCtaDe']
76 1
                if parent:
77 1
                    p_cuenta = cuentas[parent]
78 1
                    p_cuenta['Debe'] += v['Debe']
79 1
                    p_cuenta['Haber'] += v['Haber']
80
81
    # Fill SaldoFin
82 1
    for c in cuentas.values():
83 1
        if c["Natur"] == "D":
84 1
            c["SaldoFin"] += c["SaldoIni"] + c["Debe"] - c["Haber"]
85
        else:
86
            c["SaldoFin"] += c["SaldoIni"] + c["Haber"] - c["Debe"]
87
88
89 1
def generar_contabilidad(
90
        dp: DatePeriod,
91
        rfc_emisor: str,
92
        cuentas: dict,
93
        polizas: Sequence[Poliza],
94
        tipo_envio='N',
95
        fecha_mod_bal=None,
96
        tipo_solicitud='',
97
        numero_orden=None,
98
        numero_tramite=None,
99
        folder=None,
100
        fiel=None,
101
        generate_pdf=False):
102
103 1
    validate_polizas(polizas)
104 1
    calcular_saldos(cuentas, polizas)
105
106 1
    plz = Polizas(
107
        rfc=rfc_emisor,
108
        mes=str(dp.month).zfill(2),
109
        anio=dp.year,
110
        tipo_solicitud=tipo_solicitud,
111
        num_orden=numero_orden,
112
        num_tramite=numero_tramite,
113
        poliza=polizas
114
    )
115 1
    output_file(plz, folder, fiel, generate_pdf=generate_pdf)
116
117 1
    cat = Catalogo(
118
        rfc=rfc_emisor,
119
        mes=str(dp.month).zfill(2),
120
        anio=dp.year,
121
        ctas=[
122
            Ctas(
123
                cod_agrup=v["CodAgrup"].split("_")[0],
124
                num_cta=k,
125
                desc=v["Desc"],
126
                nivel=v["Nivel"],
127
                natur=v["Natur"],
128
                sub_cta_de=v['SubCtaDe'],
129
            ) for k, v in cuentas.items()
130
        ]
131
    )
132 1
    cato = output_file(cat, folder, fiel)
133
134 1
    ban = Balanza(
135
        rfc=rfc_emisor,
136
        mes=str(dp.month).zfill(2),
137
        anio=dp.year,
138
        tipo_envio=tipo_envio,
139
        fecha_mod_bal=fecha_mod_bal,
140
        ctas=[{
141
            "NumCta": k,
142
            **v,
143
        } for k, v in cuentas.items() if v["SaldoIni"] or v["Debe"] or v["Haber"] or v["SaldoFin"]],
144
    )
145 1
    bano = output_file(ban, folder, fiel)
146
147 1
    aux_detalles = group_aux_cuentas(polizas)
148 1
    aux = AuxiliarCtas(
149
        rfc=rfc_emisor,
150
        mes=str(dp.month).zfill(2),
151
        anio=dp.year,
152
        tipo_solicitud=tipo_solicitud,
153
        num_orden=numero_orden,
154
        num_tramite=numero_tramite,
155
        cuenta=[
156
            Cuenta(
157
                num_cta=k,
158
                des_cta=v["Desc"],
159
                saldo_ini=v["SaldoIni"],
160
                saldo_fin=v["SaldoFin"],
161
                detalle_aux=aux_detalles[k]
162
            ) for k, v in cuentas.items() if k in aux_detalles
163
        ]
164
    )
165 1
    output_file(aux, folder, fiel, generate_pdf=generate_pdf)
166
167 1
    auxf = RepAuxFol(
168
        rfc=rfc_emisor,
169
        mes=str(dp.month).zfill(2),
170
        anio=dp.year,
171
        tipo_solicitud=tipo_solicitud,
172
        num_orden=numero_orden,
173
        num_tramite=numero_tramite,
174
        det_aux_fol=list(group_aux_folios(polizas))
175
    )
176 1
    output_file(auxf, folder, fiel, generate_pdf=generate_pdf)
177
178 1
    imprimir_contablidad(
179
        catalogo_cuentas=cato,
180
        balanza_comprobacion=bano,
181
        archivo_excel=os.path.join(folder, filename(ban)[:-4] + ".xlsx")
182
    )
183
184 1
    validate_saldos(cuentas)
185
186
187 1
def group_aux_cuentas(polizas):
188 1
    cta_polizas = {}
189 1
    for p in polizas:
190 1
        for t in p["Transaccion"]:
191 1
            detalles = cta_polizas.setdefault(t["NumCta"], [])
192 1
            detalles.append(
193
                DetalleAux(
194
                    fecha=p["Fecha"],
195
                    num_un_iden_pol=p["NumUnIdenPol"],
196
                    concepto=p["Concepto"] + " " + t["Concepto"],
197
                    debe=t["Debe"],
198
                    haber=t["Haber"],
199
                )
200
            )
201 1
    return cta_polizas
202
203
204 1
def group_aux_folios(polizas):
205 1
    for p in polizas:
206 1
        compr_nal = []
207 1
        compr_nal_otr = []
208 1
        compr_ext = []
209
210 1
        for t in p["Transaccion"]:
211 1
            if c := t.get('CompNal'):
212 1
                for c in iterate(c):
213 1
                    if c not in compr_nal:
214 1
                        compr_nal.append(c)
215 1
            if c := t.get('CompNalOtr'):
216
                for c in iterate(c):
217
                    if c not in compr_nal_otr:
218
                        compr_nal_otr.append(c)
219 1
            if c := t.get('CompExt'):
220
                for c in iterate(c):
221
                    if c not in compr_ext:
222
                        compr_ext.append(c)
223
224 1
        yield DetAuxFol(
225
            num_un_iden_pol=p["NumUnIdenPol"],
226
            fecha=p["Fecha"],
227
            compr_nal=compr_nal,
228
            compr_nal_otr=compr_nal_otr,
229
            compr_ext=compr_ext,
230
        )
231
232
233 1
def validate_saldos(cuentas):
234 1
    total = 0
235 1
    totales = {}
236 1
    for k, v in cuentas.items():
237 1
        if v['Nivel'] == 1:
238 1
            if v['Natur'] == 'D':
239 1
                total += v['SaldoFin']
240
            else:
241
                total -= v['SaldoFin']
242
        else:
243 1
            totales.setdefault(v['SubCtaDe'], 0)
244 1
            if v['Natur'] == 'D':
245 1
                totales[v['SubCtaDe']] += v['SaldoFin']
246
            else:
247
                totales[v['SubCtaDe']] -= v['SaldoFin']
248
249 1
    assert total == 0
250 1
    for k, v in totales.items():
251 1
        if cuentas[k]['Natur'] == 'D':
252 1
            if v != cuentas[k]['SaldoFin']:
253
                raise ValueError(f"Error in {k}: {v} != {cuentas[k]['SaldoFin']}")
254
        else:
255
            if v != -cuentas[k]['SaldoFin']:
256
                raise ValueError(f"Error in {k}: {v} != {cuentas[k]['SaldoFin']}")
257
258
259 1
def validate_polizas(polizas):
260 1
    num_un = set()
261 1
    for p in polizas:
262 1
        u = p['NumUnIdenPol']
263 1
        if u in num_un:
264
            raise ValueError(f"Repeated NumUnIdenPol: {u}")
265
        num_un.add(u)
266