empresa-libre/source/app/controllers/util.py

916 lines
27 KiB
Python
Raw Normal View History

2017-06-27 15:43:02 -05:00
#!/usr/bin/env python
import datetime
import getpass
2017-10-08 22:01:19 -05:00
import hashlib
2017-06-27 15:43:02 -05:00
import json
import mimetypes
import os
import re
2017-09-30 23:14:44 -05:00
import sqlite3
2017-10-15 02:30:55 -05:00
import socket
2017-10-08 22:01:19 -05:00
import subprocess
import tempfile
2017-10-15 02:30:55 -05:00
import time
import unicodedata
2017-06-27 15:43:02 -05:00
import uuid
2017-10-15 02:30:55 -05:00
from xml.etree import ElementTree as ET
import uno
from com.sun.star.beans import PropertyValue
2017-10-15 17:20:20 -05:00
from com.sun.star.awt import Size
2017-06-27 15:43:02 -05:00
2017-10-15 17:20:20 -05:00
import pyqrcode
2017-10-08 22:01:19 -05:00
from dateutil import parser
2017-06-27 15:43:02 -05:00
2017-10-15 02:30:55 -05:00
from .helper import CaseInsensitiveDict, NumLet
2017-10-10 18:49:05 -05:00
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
2017-10-15 02:30:55 -05:00
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PRE
2017-06-27 15:43:02 -05:00
#~ def _get_hash(password):
#~ return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
2017-06-27 15:43:02 -05:00
#~ def validate_password(hashed, password):
#~ return bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode()
2017-06-27 15:43:02 -05:00
2017-10-08 22:01:19 -05:00
def _call(args):
return subprocess.check_output(args, shell=True).decode()
2017-10-10 18:49:05 -05:00
def _get_md5(data):
return hashlib.md5(data.encode()).hexdigest()
2017-10-08 22:01:19 -05:00
def _save_temp(data, modo='wb'):
path = tempfile.mkstemp()[1]
with open(path, modo) as f:
f.write(data)
return path
2017-10-10 18:49:05 -05:00
def _join(*paths):
return os.path.join(*paths)
def _kill(path):
try:
os.remove(path)
except:
pass
return
2017-06-27 15:43:02 -05:00
def get_pass():
password = getpass.getpass('Introduce la contraseƱa: ')
pass2 = getpass.getpass('Confirma la contraseƱa: ')
if password != pass2:
msg = 'Las contraseƱas son diferentes'
return False, msg
password = password.strip()
if not password:
msg = 'La contraseƱa es necesaria'
return False, msg
2017-09-30 23:14:44 -05:00
return True, password
def get_value(arg):
value = input('Introduce el {}: '.format(arg)).strip()
if not value:
msg = 'El {} es requerido'.format(arg)
log.error(msg)
return ''
return value
def _get_args(rfc):
con = sqlite3.connect(COMPANIES)
cursor = con.cursor()
sql = "SELECT con FROM names WHERE rfc=?"
cursor.execute(sql, (rfc,))
values = cursor.fetchone()
if values is None:
msg = 'No se encontrĆ³ el RFC'
log.error(msg)
return ''
cursor.close()
con.close()
return values[0]
def get_con(rfc=''):
if not rfc:
rfc = get_value('RFC').upper()
if not rfc:
return False
args = _get_args(rfc.upper())
if not args:
return False
return loads(args)
def get_rfcs():
con = sqlite3.connect(COMPANIES)
cursor = con.cursor()
sql = "SELECT * FROM names"
cursor.execute(sql)
values = cursor.fetchall()
cursor.close()
con.close()
return values
2017-06-27 15:43:02 -05:00
2017-10-04 00:11:49 -05:00
def get_sat_key(table, key):
con = sqlite3.connect(DB_SAT)
cursor = con.cursor()
sql = 'SELECT key, description FROM {} WHERE key=?'.format(table)
cursor.execute(sql, (key,))
data = cursor.fetchone()
cursor.close()
con.close()
if data is None:
return {'ok': False, 'text': 'No se encontrĆ³ la clave'}
return {'ok': True, 'text': data[1]}
2017-06-27 15:43:02 -05:00
def now():
2017-10-07 00:16:58 -05:00
return datetime.datetime.now().replace(microsecond=0)
2017-06-27 15:43:02 -05:00
def get_token():
return _get_hash(uuid.uuid4().hex)
def get_mimetype(path):
mt = mimetypes.guess_type(path)[0]
return mt or 'application/octet-stream'
def is_file(path):
return os.path.isfile(path)
def get_stream(path):
return get_file(path), get_size(path)
def get_file(path):
return open(path, 'rb')
def get_size(path):
return os.path.getsize(path)
def get_template(name, data={}):
#~ print ('NAME', name, data)
template = template_lookup.get_template(name)
return template.render(**data)
2017-10-15 02:30:55 -05:00
def get_path_template(name, default='plantilla_factura.ods'):
path = _join(PATH_TEMPLATES, name)
if is_file(path):
return path
path = _join(PATH_TEMPLATES, default)
if is_file(path):
return path
return ''
2017-06-27 15:43:02 -05:00
def dumps(data):
return json.dumps(data, default=str)
2017-09-30 23:14:44 -05:00
def loads(data):
return json.loads(data)
2017-06-27 15:43:02 -05:00
def clean(values):
for k, v in values.items():
if isinstance(v, str):
values[k] = v.strip()
return values
2017-09-30 00:22:55 -05:00
def parse_con(values):
data = values.split('|')
try:
con = {'type': data[0]}
if con['type'] == 'sqlite':
con['name'] = data[1]
else:
if data[1]:
con['host'] = data[1]
if data[2]:
con['port'] = data[2]
con['name'] = data[3]
con['user'] = data[4]
con['password'] = data[5]
return con
except IndexError:
return {}
def spaces(value):
return ' '.join(value.split())
def to_slug(string):
value = (unicodedata.normalize('NFKD', string)
.encode('ascii', 'ignore')
.decode('ascii').lower())
2017-10-15 02:30:55 -05:00
return value.replace(' ', '_')
2017-10-08 22:01:19 -05:00
class Certificado(object):
def __init__(self, key, cer):
self._key = key
self._cer = cer
self._modulus = ''
self._save_files()
self.error = ''
def _save_files(self):
try:
self._path_key = _save_temp(self._key)
self._path_cer = _save_temp(self._cer)
except:
self._path_key = ''
self._path_cer = ''
return
def _kill(self, path):
try:
os.remove(path)
except:
pass
return
def _get_info_cer(self, session_rfc):
data = {}
args = 'openssl x509 -inform DER -in {}'
try:
cer_pem = _call(args.format(self._path_cer))
except Exception as e:
self.error = 'No se pudo convertir el CER en PEM'
return data
args = 'openssl enc -base64 -in {}'
try:
cer_txt = _call(args.format(self._path_cer))
except Exception as e:
self.error = 'No se pudo convertir el CER en TXT'
return data
args = 'openssl x509 -inform DER -in {} -noout -{}'
try:
result = _call(args.format(self._path_cer, 'purpose')).split('\n')[3]
except Exception as e:
self.error = 'No se puede saber si es FIEL'
return data
if result == 'SSL server : No':
self.error = 'El certificado es FIEL'
return data
result = _call(args.format(self._path_cer, 'serial'))
serie = result.split('=')[1].split('\n')[0][1::2]
result = _call(args.format(self._path_cer, 'subject'))
rfc = result.split('=')[5].split('/')[0].strip()
if not DEBUG:
if not rfc == session_rfc:
self.error = 'El RFC del certificado no corresponde.'
return data
dates = _call(args.format(self._path_cer, 'dates')).split('\n')
desde = parser.parse(dates[0].split('=')[1])
hasta = parser.parse(dates[1].split('=')[1])
self._modulus = _call(args.format(self._path_cer, 'modulus'))
data['cer'] = self._cer
data['cer_tmp'] = None
data['cer_pem'] = cer_pem
data['cer_txt'] = cer_txt.replace('\n', '')
data['serie'] = serie
data['rfc'] = rfc
data['desde'] = desde
data['hasta'] = hasta
return data
def _get_p12(self, password, rfc):
tmp_cer = tempfile.mkstemp()[1]
tmp_key = tempfile.mkstemp()[1]
tmp_p12 = tempfile.mkstemp()[1]
args = 'openssl x509 -inform DER -in "{}" -out "{}"'
_call(args.format(self._path_cer, tmp_cer))
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} -out "{}"'
_call(args.format(self._path_key, password, tmp_key))
args = 'openssl pkcs12 -export -in "{}" -inkey "{}" -name "{}" -passout ' \
'pass:"{}" -out "{}"'
_call(args.format(tmp_cer, tmp_key, rfc,
hashlib.md5(rfc.encode()).hexdigest(), tmp_p12))
data = open(tmp_p12, 'rb').read()
self._kill(tmp_cer)
self._kill(tmp_key)
self._kill(tmp_p12)
return data
def _get_info_key(self, password, rfc):
data = {}
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{}'
try:
result = _call(args.format(self._path_key, password))
except Exception as e:
self.error = 'ContraseƱa incorrecta'
return data
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} | ' \
'openssl rsa -noout -modulus'
mod_key = _call(args.format(self._path_key, password))
if self._modulus != mod_key:
self.error = 'Los archivos no son pareja'
return data
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} | ' \
'openssl rsa -des3 -passout pass:{}'.format(
2017-10-10 18:49:05 -05:00
self._path_key, password, _get_md5(rfc))
2017-10-08 22:01:19 -05:00
key_enc = _call(args)
data['key'] = self._key
data['key_tmp'] = None
data['key_enc'] = key_enc
data['p12'] = self._get_p12(password, rfc)
return data
def validate(self, password, rfc):
if not self._path_key or not self._path_cer:
self.error = 'Error al cargar el certificado'
return {}
data = self._get_info_cer(rfc)
2017-10-10 18:49:05 -05:00
llave = self._get_info_key(password, data['rfc'])
2017-10-08 22:01:19 -05:00
if not llave:
return {}
data.update(llave)
self._kill(self._path_key)
self._kill(self._path_cer)
return data
2017-10-10 18:49:05 -05:00
def make_xml(data, certificado):
from .cfdi_xml import CFDI
if DEBUG:
data['emisor']['Rfc'] = certificado.rfc
data['emisor']['RegimenFiscal'] = '603'
cfdi = CFDI()
xml = cfdi.get_xml(data)
data = {
'xsltproc': PATH_XSLTPROC,
'xslt': _join(PATH_XSLT, 'cadena.xslt'),
'xml': _save_temp(xml, 'w'),
'openssl': PATH_OPENSSL,
'key': _save_temp(certificado.key_enc, 'w'),
'pass': _get_md5(certificado.rfc)
}
args = '"{xsltproc}" "{xslt}" "{xml}" | ' \
'"{openssl}" dgst -sha256 -sign "{key}" -passin pass:{pass} | ' \
'"{openssl}" enc -base64 -A'.format(**data)
sello = _call(args)
_kill(data['xml'])
_kill(data['key'])
return cfdi.add_sello(sello)
def timbra_xml(xml):
from .pac import Finkok as PAC
result = {'ok': True, 'error': ''}
pac = PAC()
xml = pac.timbra_xml(xml)
if not xml:
result['ok'] = False
result['error'] = pac.error
return result
result['xml'] = xml
result['uuid'] = pac.uuid
result['fecha'] = pac.fecha
return result
2017-10-15 02:30:55 -05:00
class LIBO(object):
HOST = 'localhost'
PORT = '8100'
ARG = 'socket,host={},port={};urp;StarOffice.ComponentContext'.format(
HOST, PORT)
def __init__(self):
self._app = None
self._start_office()
self._init_values()
def _init_values(self):
self._ctx = None
self._sm = None
self._desktop = None
if self.is_running:
ctx = uno.getComponentContext()
service = 'com.sun.star.bridge.UnoUrlResolver'
resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx)
self._ctx = resolver.resolve('uno:{}'.format(self.ARG))
self._sm = self._ctx.ServiceManager
self._desktop = self._create_instance('com.sun.star.frame.Desktop')
return
def _create_instance(self, name, with_context=True):
if with_context:
instance = self._sm.createInstanceWithContext(name, self._ctx)
else:
instance = self._sm.createInstance(name)
return instance
@property
def is_running(self):
try:
s = socket.create_connection((self.HOST, self.PORT), 5.0)
s.close()
return True
except ConnectionRefusedError:
return False
def _start_office(self):
if self.is_running:
return
c = 1
while c < 4:
c += 1
self.app = subprocess.Popen([
'soffice', '--headless', '--accept={}'.format(self.ARG)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
time.sleep(5)
if self.is_running:
return
return
def _set_properties(self, properties):
pl = []
for k, v in properties.items():
pv = PropertyValue()
pv.Name = k
pv.Value = v
pl.append(pv)
return tuple(pl)
def _doc_open(self, path, options):
options = self._set_properties(options)
path = self._path_url(path)
try:
doc = self._desktop.loadComponentFromURL(path, '_blank', 0, options)
return doc
except:
return None
def _path_url(self, path):
if path.startswith('file://'):
return path
return uno.systemPathToFileUrl(path)
def close(self):
if self.is_running:
if not self._desktop is None:
self._desktop.terminate()
if not self._app is None:
self._app.terminate()
return
def _read(self, path):
try:
return open(path, 'rb').read()
except:
return b''
def _clean(self):
self._sd.SearchRegularExpression = True
self._sd.setSearchString("\{(\w.+)\}")
self._search.replaceAll(self._sd)
return
def _cancelado(self, cancel):
if not cancel:
pd = self._sheet.getDrawPage()
if pd.getCount():
pd.remove(pd.getByIndex(0))
return
def _set_search(self):
self._sheet = self._template.getSheets().getByIndex(0)
self._search = self._sheet.getPrintAreas()[0]
self._search = self._sheet.getCellRangeByPosition(
self._search.StartColumn,
self._search.StartRow,
self._search.EndColumn,
self._search.EndRow
)
self._sd = self._sheet.createSearchDescriptor()
self._sd.SearchCaseSensitive = False
return
2017-10-15 17:20:20 -05:00
def _next_cell(self, cell):
col = cell.getCellAddress().Column
row = cell.getCellAddress().Row + 1
return self._sheet.getCellByPosition(col, row)
def _copy_cell(self, cell):
destino = self._next_cell(cell)
self._sheet.copyRange(destino.getCellAddress(), cell.getRangeAddress())
return destino
2017-10-15 02:30:55 -05:00
def _set_cell(self, k='', v=None, cell=None, value=False):
if k:
self._sd.setSearchString(k)
ranges = self._search.findAll(self._sd)
if ranges:
ranges = ranges.getRangeAddressesAsString().split(';')
for r in ranges:
for c in r.split(','):
cell = self._sheet.getCellRangeByName(c)
if v is None:
return cell
if cell.getImplementationName() == 'ScCellObj':
pattern = re.compile(k, re.IGNORECASE)
nv = pattern.sub(v, cell.getString())
if value:
cell.setValue(nv)
else:
cell.setString(nv)
return cell
if cell:
if cell.getImplementationName() == 'ScCellObj':
ca = cell.getCellAddress()
new_cell = self._sheet.getCellByPosition(ca.Column, ca.Row + 1)
if value:
new_cell.setValue(v)
else:
new_cell.setString(v)
return new_cell
def _comprobante(self, data):
for k, v in data.items():
if k in ('total', 'descuento', 'subtotal'):
self._set_cell('{cfdi.%s}' % k, v, value=True)
else:
self._set_cell('{cfdi.%s}' % k, v)
return
2017-10-15 17:20:20 -05:00
def _emisor(self, data):
for k, v in data.items():
self._set_cell('{emisor.%s}' % k, v)
return
def _receptor(self, data):
for k, v in data.items():
self._set_cell('{receptor.%s}' % k, v)
return
def _conceptos(self, data):
first = True
for concepto in data:
key = concepto.get('noidentificacion', '')
description = concepto['descripcion']
unidad = concepto['unidad']
cantidad = concepto['cantidad']
valor_unitario = concepto['valorunitario']
importe = concepto['importe']
if first:
first = False
cell_1 = self._set_cell('{noidentificacion}', key)
cell_2 = self._set_cell('{descripcion}', description)
cell_3 = self._set_cell('{unidad}', unidad)
cell_4 = self._set_cell('{cantidad}', cantidad, value=True)
cell_5 = self._set_cell('{valorunitario}', valor_unitario, value=True)
cell_6 = self._set_cell('{importe}', importe, value=True)
return
def _totales(self, data):
currency = data['moneda']
cell_title = self._set_cell('{subtotal.titulo}', 'SubTotal')
value = data['subtotal']
cell_value = self._set_cell('{subtotal}', value, value=True)
cell_value.CellStyle = currency
#~ Si encuentra el campo {total}, se asume que los totales e impuestos
#~ estƔn declarados de forma independiente cada uno
#~ if self._add_totales(xml):
#~ return
#~ Si no se encuentra, copia las celdas hacia abajo de
#~ {subtotal.titulo} y {subtotal}
if 'descuento' in data:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v='Descuento', cell=cell_title)
value = data['descuento']
cell_value = self._set_cell(v=value, cell=cell_value, value=True)
cell_value.CellStyle = currency
for tax in data['traslados']:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v=tax[0], cell=cell_title)
cell_value = self._set_cell(v=tax[1], cell=cell_value, value=True)
cell_value.CellStyle = currency
for tax in data['retenciones']:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v=tax[0], cell=cell_title)
cell_value = self._set_cell(v=tax[1], cell=cell_value, value=True)
cell_value.CellStyle = currency
for tax in data['taxlocales']:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v=tax[0], cell=cell_title)
cell_value = self._set_cell(v=tax[1], cell=cell_value, value=True)
cell_value.CellStyle = currency
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v='Total', cell=cell_title)
value = data['total']
cell_value = self._set_cell(v=value, cell=cell_value, value=True)
cell_value.CellStyle = currency
return
2017-10-15 02:30:55 -05:00
def _timbre(self, data):
for k, v in data.items():
self._set_cell('{timbre.%s}' % k, v)
2017-10-15 17:20:20 -05:00
pd = self._sheet.getDrawPage()
image = self._template.createInstance('com.sun.star.drawing.GraphicObjectShape')
image.GraphicURL = data['path_cbb']
pd.add(image)
s = Size()
s.Width = 4250
s.Height = 4500
image.setSize(s)
image.Anchor = self._set_cell('{timbre.cbb}')
2017-10-15 02:30:55 -05:00
return
def _render(self, data):
self._set_search()
self._comprobante(data['comprobante'])
2017-10-15 17:20:20 -05:00
self._emisor(data['emisor'])
self._receptor(data['receptor'])
self._conceptos(data['conceptos'])
self._totales(data['totales'])
2017-10-15 02:30:55 -05:00
self._timbre(data['timbre'])
2017-10-15 17:20:20 -05:00
self._cancelado(data['cancelada'])
self._clean()
2017-10-15 02:30:55 -05:00
return
def pdf(self, path, data):
options = {'AsTemplate': True, 'Hidden': True}
self._template = self._doc_open(path, options)
if self._template is None:
return b''
self._render(data)
2017-10-15 17:20:20 -05:00
path = '{}.ods'.format(tempfile.mkstemp()[1])
self._template.storeToURL(self._path_url(path), ())
doc = self._doc_open(path, {'Hidden': True})
2017-10-15 02:30:55 -05:00
options = {'FilterName': 'calc_pdf_Export'}
path = tempfile.mkstemp()[1]
2017-10-15 17:20:20 -05:00
doc.storeToURL(self._path_url(path), self._set_properties(options))
doc.close(True)
2017-10-15 02:30:55 -05:00
self._template.close(True)
return self._read(path)
def to_pdf(path, data):
app = LIBO()
if not app.is_running:
return b''
return app.pdf(path, data)
def parse_xml(xml):
return ET.fromstring(xml)
def get_dict(data):
return CaseInsensitiveDict(data)
def to_letters(value, moneda):
monedas = {
'MXN': 'peso',
2017-10-15 17:20:20 -05:00
'USD': 'dĆ³lar',
'EUR': 'euro',
2017-10-15 02:30:55 -05:00
}
return NumLet(value, monedas[moneda]).letras
def get_qr(data):
path = tempfile.mkstemp()[1]
2017-10-15 17:20:20 -05:00
qr = pyqrcode.create(data, mode='binary')
qr.png(path, scale=7)
2017-10-15 02:30:55 -05:00
return path
2017-10-15 17:20:20 -05:00
def _comprobante(values, options):
2017-10-15 02:30:55 -05:00
data = CaseInsensitiveDict(values)
del data['certificado']
2017-10-15 17:20:20 -05:00
2017-10-15 02:30:55 -05:00
data['totalenletras'] = to_letters(float(data['total']), data['moneda'])
if data['version'] == '3.3':
tipos = {
'I': 'ingreso',
'E': 'egreso',
'T': 'traslado',
}
data['tipodecomprobante'] = tipos.get(data['tipodecomprobante'])
data['lugarexpedicion'] = 'C.P. ExpediciĆ³n: {}'.format(data['lugarexpedicion'])
2017-10-15 17:20:20 -05:00
data['metododepago'] = options['metododepago']
data['formadepago'] = options['formadepago']
data['moneda'] = options['moneda']
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
float(data['tipocambio']))
2017-10-15 02:30:55 -05:00
return data
2017-10-15 17:20:20 -05:00
def _emisor(doc, version, values):
node = doc.find('{}Emisor'.format(PRE[version]))
data = CaseInsensitiveDict(node.attrib.copy())
node = node.find('{}DomicilioFiscal'.format(PRE[version]))
if not node is None:
data.update(node.attrib.copy())
data['regimenfiscal'] = values['regimenfiscal']
return data
def _receptor(doc, version, values):
node = doc.find('{}Receptor'.format(PRE[version]))
data = CaseInsensitiveDict(node.attrib.copy())
node = node.find('{}Domicilio'.format(PRE[version]))
if not node is None:
data.update(node.attrib.copy())
data['usocfdi'] = values['usocfdi']
return data
def _conceptos(doc, version):
data = []
conceptos = doc.find('{}Conceptos'.format(PRE[version]))
for c in conceptos.getchildren():
values = CaseInsensitiveDict(c.attrib.copy())
if version == '3.3':
values['noidentificacion'] = '{}\n(SAT {})'.format(
values['noidentificacion'], values['ClaveProdServ'])
values['unidad'] = '({}) {}'.format(
values['ClaveUnidad'], values['unidad'])
data.append(values)
return data
def _totales(doc, cfdi, version):
data = {}
data['moneda'] = doc.attrib['Moneda']
data['subtotal'] = cfdi['subtotal']
if 'descuento' in cfdi:
data['descuento'] = cfdi['descuento']
data['total'] = cfdi['total']
tn = {
'001': 'ISR',
'002': 'IVA',
}
traslados = []
retenciones = []
taxlocales = []
imp = doc.find('{}Impuestos'.format(PRE[version]))
if imp is not None:
tmp = CaseInsensitiveDict(imp.attrib.copy())
for k, v in tmp.items():
data[k] = v
node = imp.find('{}Traslados'.format(PRE[version]))
if node is not None:
for n in node.getchildren():
tmp = CaseInsensitiveDict(n.attrib.copy())
if version == '3.3':
title = 'Traslado {} {}'.format(
tn.get(tmp['impuesto']), tmp['tasaocuota'])
else:
title = 'Traslado {} {}'.format(tmp['impuesto'], tmp['tasa'])
traslados.append((title, float(tmp['importe'])))
node = imp.find('{}Retenciones'.format(PRE[version]))
if node is not None:
for n in node.getchildren():
tmp = CaseInsensitiveDict(n.attrib.copy())
if version == '3.3':
title = 'RetenciĆ³n {} {}'.format(
tn.get(tmp['impuesto']), '')
else:
title = 'RetenciĆ³n {} {}'.format(tmp['impuesto'], '')
retenciones.append((title, float(tmp['importe'])))
#~ com = xml.find('%sComplemento' % PRE)
#~ if com is not None:
#~ otros = com.find('%sImpuestosLocales' % IMP_LOCAL)
#~ if otros is not None:
#~ for otro in list(otros):
#~ if otro.tag == '%sRetencionesLocales' % IMP_LOCAL:
#~ name = 'ImpLocRetenido'
#~ tasa = 'TasadeRetencion'
#~ else:
#~ name = 'ImpLocTrasladado'
#~ tasa = 'TasadeTraslado'
#~ title = '%s %s %%' % (otro.attrib[name], otro.attrib[tasa])
#~ value = otro.attrib['Importe']
#~ self._copy_cell(cell_title)
#~ self._copy_cell(cell_value)
#~ cell_title = self._set_cell(v=title, cell=cell_title)
#~ cell_value = self._set_cell(v=value, cell=cell_value, value=True)
#~ cell_value.CellStyle = currency
data['traslados'] = traslados
data['retenciones'] = retenciones
data['taxlocales'] = taxlocales
return data
2017-10-15 02:30:55 -05:00
def _timbre(doc, version, values):
CADENA = '||{version}|{UUID}|{FechaTimbrado}|{selloCFD}|{noCertificadoSAT}||'
if version == '3.3':
CADENA = '||{Version}|{UUID}|{FechaTimbrado}|{SelloCFD}|{NoCertificadoSAT}||'
node = doc.find('{}Complemento/{}TimbreFiscalDigital'.format(
PRE[version], PRE['TIMBRE']))
data = CaseInsensitiveDict(node.attrib.copy())
total_s = '%017.06f' % float(values['total'])
qr_data = '?re=%s&rr=%s&tt=%s&id=%s' % (
values['rfc_emisor'],
values['rfc_receptor'],
total_s,
node.attrib['UUID'])
2017-10-15 17:20:20 -05:00
data['path_cbb'] = get_qr(qr_data)
2017-10-15 02:30:55 -05:00
data['cadenaoriginal'] = CADENA.format(**node.attrib)
return data
2017-10-15 17:20:20 -05:00
def get_data(invoice, rfc, values):
2017-10-15 02:30:55 -05:00
name = '{}_factura.ods'.format(rfc.lower())
path = get_path_template(name)
data = {'cancelada': invoice.cancelada}
doc = parse_xml(invoice.xml)
2017-10-15 17:20:20 -05:00
data['comprobante'] = _comprobante(doc.attrib.copy(), values)
2017-10-15 02:30:55 -05:00
version = data['comprobante']['version']
2017-10-15 17:20:20 -05:00
data['emisor'] = _emisor(doc, version, values)
data['receptor'] = _receptor(doc, version, values)
data['conceptos'] = _conceptos(doc, version)
data['totales'] = _totales(doc, data['comprobante'], version)
options = {
'rfc_emisor': data['emisor']['rfc'],
'rfc_receptor': data['receptor']['rfc'],
'total': data['comprobante']['total'],
}
data['timbre'] = _timbre(doc, version, options)
2017-10-15 02:30:55 -05:00
return path, data