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
|
|
|
|