cfdi-admin/source/main/util/util.py

435 lines
12 KiB
Python

#!/usr/bin/env python3
import io
import zipfile
from pathlib import Path
from django.conf import settings
from .cfdi_to_dict import CfdiToDic
from . import easymacro as app
class NumLet(object):
CURRENCIES = {
'MXN': 'peso',
'USD': 'dólar',
'EUR': 'euro',
}
def __init__(self, value, currency='MXN', **kwargs):
self._letters = self._convert(value, currency, kwargs)
@property
def letters(self):
return self._letters
def _convert(self, value, currency, args):
letters = ''
currency = self.CURRENCIES.get(currency.upper(), 'peso')
text_start = args.get('text_start', '-( ')
plural = self._plural(currency)
template = f'{int(value):0>15}'
decimals = f'{value:0.12f}'.split('.')[1][:2]
text_end = args.get('text_end', f' {decimals}/100 m.n. )-')
if value < 1:
letters = f'cero '
elif value < 2:
plural = currency
letters = f'un '
else:
letters = self._to_letters(template)
if int(''.join(template[3:])) == 0 or int(''.join(template[9:])) == 0:
plural = f'de {plural}'
letters = f'{text_start}{letters}{plural}{text_end}'
return letters
def _to_letters(self, template):
letters = ''
for i in range(0, 15, 3):
cen = int(template[i])
dec = int(template[i+1])
uni = int(template[i+2])
letter3 = self._centena(uni, dec, cen)
letter2 = self._decena(uni, dec)
letter1 = self._unidad(uni, dec)
legend = ''
if i == 0:
if (cen + dec + uni) == 1:
legend = 'billon '
elif (cen + dec + uni) > 1:
legend = 'billones '
elif i == 3:
if (cen + dec + uni) >= 1 and int(''.join(template[6:9])) == 0:
legend = "mil millones "
elif (cen + dec + uni) >= 1:
legend = "mil "
elif i == 6:
if (cen + dec) == 0 and uni == 1:
legend = 'millon '
elif cen > 0 or dec > 0 or uni > 1:
legend = 'millones '
elif i == 9:
if (cen + dec + uni) >= 1:
legend = 'mil '
letters += letter3 + letter2 + letter1 + legend
return letters
def _centena(self, uni, dec, cen):
numbers = ('', 'ciento ', 'doscientos ', 'trescientos ', 'cuatrocientos ',
'quinientos ', 'seiscientos ', 'setecientos ', 'ochocientos ',
'novecientos ')
letters = numbers[cen]
if cen == 1 and (dec + uni) == 0:
letters = 'cien '
return letters
def _decena(self, uni, dec):
numbers = ('diez ', 'once ', 'doce ', 'trece ', 'catorce ', 'quince ', 'dieci')
decenas = ('', '', '', 'treinta ', 'cuarenta ', 'cincuenta ',
'sesenta ', 'setenta ', 'ochenta ', 'noventa ')
letters = decenas[dec]
if dec == 1:
if uni > 5:
letters = numbers[-1]
else:
letters = numbers[uni]
elif dec == 2:
letters = 'veinti'
if uni == 0:
letters = 'veinte '
if uni > 0 and dec > 2:
letters = letters + 'y '
return letters
def _unidad(self, uni, dec):
letters = ''
numbers = ('', 'un ', 'dos ', 'tres ', 'cuatro ', 'cinco ',
'seis ', 'siete ', 'ocho ', 'nueve ')
if dec != 1:
if uni > 0 and uni <= 5:
letters = numbers[uni]
if uni >= 6 and uni <= 9:
letters = numbers[uni]
return letters
def _plural(self, word):
if word[-1] in 'aeiou':
word += 's'
else:
word += 'es'
return word
def get_data_from_cfdi(xml):
cfdi = CfdiToDic()
cfdi.complements = False
cfdi.parse(xml)
data = {}
source = cfdi.data['comprobante']
fields = (
('version', 'Version'),
('date_cfdi', 'Fecha'),
('type_cfdi', 'TipoDeComprobante'),
('no_cert', 'NoCertificado'),
('subtotal', 'SubTotal'),
('total', 'Total'),
('xml', 'xml'),
)
for k1, k2 in fields:
data[k1] = source[k2]
fields = (
('serie', 'Serie'),
('folio', 'Folio'),
('place_expedition', ''),
('currency', 'Moneda'),
('way_pay', 'FormaPago'),
('method_pay', 'MetodoPago'),
)
for k1, k2 in fields:
data[k1] = source.get(k2, '')
fields = (
('discount', 'Descuento'),
('type_change', 'TipoCambio'),
('tax_trasladados', 'TotalImpuestosTrasladados'),
('tax_retenidos', 'TotalImpuestosRetenidos'),
('tax_others', 'TotalOtrosImpuestos'),
)
for k1, k2 in fields:
data[k1] = source.get(k2, None)
source = cfdi.data['timbre']
fields = (
('uuid', 'UUID'),
('date_stamp', 'FechaTimbrado'),
('no_cert_sat', 'NoCertificadoSAT'),
('rfc_pac', 'RfcProvCertif'),
)
for k1, k2 in fields:
data[k1] = source[k2]
source = cfdi.data['emisor']
fields = (
('emisor_rfc', 'Rfc'),
('regimen_fiscal', 'RegimenFiscal'),
)
for k1, k2 in fields:
data[k1] = source[k2]
fields = (
('emisor', 'Nombre'),
('registro_patronal', 'RegistroPatronal'),
)
for k1, k2 in fields:
data[k1] = source.get(k1, '')
source = cfdi.data['receptor']
fields = (
('receptor_rfc', 'Rfc'),
('uso_cfdi', 'UsoCFDI'),
)
for k1, k2 in fields:
data[k1] = source[k2]
fields = (
('receptor', 'Nombre'),
)
for k1, k2 in fields:
data[k1] = source.get(k1, '')
source = cfdi.data['impuestos']
fields = (
('tax_trasladados', 'TotalImpuestosTrasladados'),
('tax_retenidos', 'TotalImpuestosRetenidos'),
('tax_others', 'TotalOtrosImpuestos'),
)
for k1, k2 in fields:
data[k1] = source.get(k2, None)
taxes = []
source = cfdi.data['impuestos'].get('traslados', {})
if source:
for t in source:
tax = dict(
type_tax = 'T',
key_sat = t['Impuesto'],
importe = t['Importe'],
type_factor = t['TipoFactor'],
rate = t['TasaOCuota'],
)
taxes.append(tax)
source = cfdi.data['impuestos'].get('retenciones', {})
if source:
for t in source:
tax = dict(
type_tax = 'R',
key_sat = t['Impuesto'],
importe = t['Importe'],
)
taxes.append(tax)
data['taxes'] = taxes
source = cfdi.data['conceptos']
details = []
for c in source:
detail = dict(
key = c.get('NoIdentificacion', ''),
key_sat = c.get('ClaveProdServ', ''),
unit = c.get('Unidad', ''),
key_unit = c.get('ClaveUnidad', ''),
description = c['Descripcion'],
cant = c['Cantidad'],
value = c['ValorUnitario'],
discount = c.get('Descuento', None),
importe = c['Importe'],
)
taxes = []
rows = c['taxes']['traslados']
for row in rows:
tax = dict(
type_tax = 'T',
base = row['Base'],
key_sat = row['Impuesto'],
importe = row['Importe'],
type_factor = row['TipoFactor'],
rate = row['TasaOCuota'],
)
taxes.append(tax)
rows = c['taxes']['retenciones']
for row in rows:
tax = dict(
type_tax = 'R',
base = row['Base'],
key_sat = row['Impuesto'],
importe = row['Importe'],
type_factor = row['TipoFactor'],
rate = row['TasaOCuota'],
)
taxes.append(tax)
detail['taxes'] = taxes
details.append(detail)
data['details'] = details
return data
def read_zip(source):
z = zipfile.ZipFile(io.BytesIO(source), compression=zipfile.ZIP_DEFLATED)
return z
def join(*paths):
return str(Path(paths[0]).joinpath(*paths[1:]))
def get_template(obj):
nomina = ''
if obj.version_nomina:
nomina = f'_{obj.version_nomina}'
name = f'{obj.emisor_rfc.lower()}_{obj.version}{nomina}.ods'
path = join(settings.MEDIA_ROOT, 'templates', name)
if not Path(path).exists():
name = f'template_{obj.version}{nomina}.ods'
path = join(settings.MEDIA_ROOT, 'default', name)
return path
def _get_data_nomina(data):
if not 'nomina' in data:
return {}
percepciones = ()
deducciones = ()
if 'percepciones' in data:
percepciones = data['percepciones'].pop('percepciones', ())
if 'deducciones' in data:
deducciones = data['deducciones'].pop('deducciones', ())
nomina = {
'percepciones': percepciones,
'deducciones': deducciones,
}
return nomina
def _set_data_nomina(doc, data):
if not data:
return
sheet = doc[0]
percepciones = data['percepciones']
deducciones = data['deducciones']
count = len(percepciones)
if len(deducciones) > count:
count = len(deducciones)
count -= 1
cells = {}
for i, row in enumerate(percepciones):
for j, k in enumerate(row.keys()):
if i == 0:
data = {'percepcion': {k: row[k]}}
cell = sheet.render(data, clean=False)
cells[k] = cell
else:
cell = cells[k]
if cell.is_none:
continue
cell = cell.next_cell
cell.value = row[k]
cells[k] = cell
if i == 0 and count:
row = cell.address.Row
rows = sheet.rows.insert(row + 1, count)
rows.copy_format_from(sheet[row])
cells = {}
for i, row in enumerate(deducciones):
for j, k in enumerate(row.keys()):
if i == 0:
data = {'deduccion': {k: row[k]}}
cell = sheet.render(data, clean=False)
cells[k] = cell
else:
cell = cells[k]
if cell.is_none:
continue
cell = cell.next_cell
cell.value = row[k]
cells[k] = cell
return
def _set_conceptos(doc, items):
if not items:
return
return
def get_pdf(obj):
items = ()
name = f'{obj.uuid}'
srv = app.LOServer()
if srv.is_running:
cfdi = CfdiToDic()
cfdi.parse(obj.xml.encode())
data = cfdi.data
total = data['comprobante']['Total']
currency = data['comprobante']['Moneda']
in_letters = NumLet(total, currency).letters.upper()
data['comprobante']['totalenletras'] = in_letters
path_template = get_template(obj)
path_ods = join('/tmp', f'{name}.ods')
path_pdf = join('/tmp', f'{name}.pdf')
args = {'AsTemplate': True, 'Hidden': True}
with app.docs.open(path_template, args) as doc:
# ~ if len(data['conceptos']) > 1:
# ~ items = data.pop('conceptos')
# ~ nomina = _get_data_nomina(data)
doc.render(data, clean=False)
# ~ _set_conceptos(doc, items)
# ~ _set_data_nomina(doc, nomina)
# ~ doc[0].shapes[0].remove()
# ~ doc.save(path_ods)
if doc.to_pdf(path_pdf):
pdf = read_bin(path_pdf)
unlink(path_ods)
unlink(path_pdf)
return pdf
def read_bin(path):
obj = io.BytesIO(Path(path).read_bytes())
obj.seek(0)
return obj
def unlink(path):
Path(path).unlink(missing_ok=True)
return