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

1721 lines
50 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
2017-11-11 13:42:51 -06:00
import locale
2017-06-27 15:43:02 -05:00
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-11-06 22:21:14 -06:00
import threading
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 18:57:25 -05:00
import zipfile
2017-10-16 00:02:51 -05:00
2017-10-15 18:57:25 -05:00
from io import BytesIO
2017-10-16 00:02:51 -05:00
from smtplib import SMTPException, SMTPAuthenticationError
2017-10-15 02:30:55 -05:00
from xml.etree import ElementTree as ET
2017-10-25 19:46:13 -05:00
try:
import uno
from com.sun.star.beans import PropertyValue
from com.sun.star.awt import Size
APP_LIBO = True
except:
APP_LIBO = False
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-23 00:45:41 -05:00
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice
2017-10-10 18:49:05 -05:00
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
2017-10-29 16:53:10 -06:00
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
2017-11-21 00:48:51 -06:00
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES
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-30 13:57:02 -06:00
def save_temp(data, modo='wb'):
2017-10-08 22:01:19 -05:00
path = tempfile.mkstemp()[1]
with open(path, modo) as f:
f.write(data)
return path
2017-11-03 14:09:34 -06:00
def save_file(path, data, modo='wb'):
2017-11-03 20:05:19 -06:00
try:
with open(path, modo) as f:
f.write(data)
return True
except:
return False
2017-11-03 14:09:34 -06:00
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
2017-10-26 13:09:47 -05:00
def _valid_db_companies():
con = sqlite3.connect(COMPANIES)
sql = """
CREATE TABLE IF NOT EXISTS names(
rfc TEXT NOT NULL COLLATE NOCASE UNIQUE,
con TEXT NOT NULL
);
"""
cursor = con.cursor()
cursor.executescript(sql)
cursor.close()
con.close()
return
2017-09-30 23:14:44 -05:00
def _get_args(rfc):
2017-10-26 13:09:47 -05:00
_valid_db_companies()
2017-09-30 23:14:44 -05:00
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]
2017-10-26 13:09:47 -05:00
def get_rfcs():
_valid_db_companies()
con = sqlite3.connect(COMPANIES)
cursor = con.cursor()
sql = "SELECT * FROM names"
cursor.execute(sql)
values = cursor.fetchall()
cursor.close()
con.close()
return values
2017-09-30 23:14:44 -05:00
def get_con(rfc=''):
if not rfc:
rfc = get_value('RFC').upper()
if not rfc:
2017-12-01 12:45:13 -06:00
return {}
2017-09-30 23:14:44 -05:00
args = _get_args(rfc.upper())
if not args:
2017-12-01 12:45:13 -06:00
return {}
2017-09-30 23:14:44 -05:00
return loads(args)
2017-10-04 00:11:49 -05:00
def get_sat_key(table, key):
con = sqlite3.connect(DB_SAT)
cursor = con.cursor()
2017-11-14 20:38:20 -06:00
sql = 'SELECT key, name FROM {} WHERE key=?'.format(table)
2017-10-04 00:11:49 -05:00
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-11-14 20:04:01 -06:00
def get_sat_unidades(key):
con = sqlite3.connect(DB_SAT)
con.row_factory = sqlite3.Row
cursor = con.cursor()
filtro = '%{}%'.format(key)
sql = "SELECT * FROM unidades WHERE key LIKE ? OR name LIKE ?"
cursor.execute(sql, [filtro, filtro])
data = cursor.fetchall()
cursor.close()
con.close()
if data is None:
2017-11-14 20:38:20 -06:00
return ()
data = [dict(r) for r in data]
return tuple(data)
def get_sat_productos(key):
con = sqlite3.connect(DB_SAT)
con.row_factory = sqlite3.Row
cursor = con.cursor()
filtro = '%{}%'.format(key)
sql = "SELECT * FROM productos WHERE key LIKE ? OR name LIKE ?"
cursor.execute(sql, [filtro, filtro])
data = cursor.fetchall()
cursor.close()
con.close()
if data is None:
return ()
2017-11-14 20:04:01 -06:00
data = [dict(r) for r in data]
return tuple(data)
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')
2017-10-23 00:45:41 -05:00
def read_file(path, mode='rb'):
return open(path, mode).read()
2017-06-27 15:43:02 -05:00
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-23 00:45:41 -05:00
def get_custom_styles(name, default='plantilla_factura.json'):
2017-10-25 19:46:13 -05:00
path = _join(PATH_MEDIA, 'templates', name.lower())
2017-10-15 02:30:55 -05:00
if is_file(path):
2017-10-23 00:45:41 -05:00
with open(path) as fh:
return loads(fh.read())
2017-10-15 02:30:55 -05:00
path = _join(PATH_TEMPLATES, default)
if is_file(path):
2017-10-23 00:45:41 -05:00
with open(path) as fh:
return loads(fh.read())
2017-10-15 02:30:55 -05:00
2017-10-23 00:45:41 -05:00
return {}
2017-10-15 02:30:55 -05:00
2017-10-25 19:46:13 -05:00
def get_template_ods(name, default='plantilla_factura.ods'):
path = _join(PATH_MEDIA, 'templates', name.lower())
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):
2017-10-30 13:57:02 -06:00
def __init__(self, paths):
self._path_key = paths['path_key']
self._path_cer = paths['path_cer']
2017-10-08 22:01:19 -05:00
self._modulus = ''
2017-10-30 13:57:02 -06:00
#~ self._save_files()
2017-10-08 22:01:19 -05:00
self.error = ''
2017-10-30 13:57:02 -06:00
#~ def _save_files(self):
#~ try:
#~ self._path_key = _save_temp(bytes(self._key))
#~ self._path_cer = _save_temp(bytes(self._cer))
#~ except Exception as e:
#~ log.error(e)
#~ self._path_key = ''
#~ self._path_cer = ''
#~ return
2017-10-08 22:01:19 -05:00
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'))
#~ Verificar si es por la version de OpenSSL
t1 = 'x500UniqueIdentifier = '
t2 = 'x500UniqueIdentifier='
if t1 in result:
rfc = result.split(t1)[1][:13].strip()
elif t2 in result:
rfc = result.split(t2)[1][:13].strip()
else:
self.error = 'No se pudo obtener el RFC del certificado'
2017-11-02 17:11:49 -06:00
print ('\n', result)
return data
2017-10-08 22:01:19 -05:00
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'))
2017-10-30 13:57:02 -06:00
data['cer'] = read_file(self._path_cer)
2017-10-08 22:01:19 -05:00
data['cer_pem'] = cer_pem
data['cer_txt'] = cer_txt.replace('\n', '')
data['serie'] = serie
data['rfc'] = rfc
data['desde'] = desde.replace(tzinfo=None)
data['hasta'] = hasta.replace(tzinfo=None)
2017-10-08 22:01:19 -05:00
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))
2017-10-30 21:56:53 -06:00
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:"{}" -out "{}"'
2017-10-08 22:01:19 -05:00
_call(args.format(self._path_key, password, tmp_key))
2017-10-30 21:56:53 -06:00
args = 'openssl pkcs12 -export -in "{}" -inkey "{}" -name "{}" ' \
'-passout pass:"{}" -out "{}"'
_call(args.format(tmp_cer, tmp_key, rfc, _get_md5(rfc), tmp_p12))
2017-10-30 13:57:02 -06:00
data = read_file(tmp_p12)
2017-10-08 22:01:19 -05:00
self._kill(tmp_cer)
self._kill(tmp_key)
self._kill(tmp_p12)
return data
def _get_info_key(self, password, rfc):
data = {}
2017-10-30 21:56:53 -06:00
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:"{}"'
2017-10-08 22:01:19 -05:00
try:
result = _call(args.format(self._path_key, password))
except Exception as e:
self.error = 'Contraseña incorrecta'
return data
2017-10-30 21:56:53 -06:00
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:"{}" | ' \
2017-10-08 22:01:19 -05:00
'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
2017-10-30 21:56:53 -06:00
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)
2017-10-30 13:57:02 -06:00
data['key'] = read_file(self._path_key)
2017-10-08 22:01:19 -05:00
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:
2017-10-30 13:57:02 -06:00
self.error = 'Error en las rutas temporales del certificado'
2017-10-08 22:01:19 -05:00
return {}
data = self._get_info_cer(rfc)
if not data:
return {}
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'),
2017-10-30 13:57:02 -06:00
'xml': save_temp(xml, 'w'),
2017-10-10 18:49:05 -05:00
'openssl': PATH_OPENSSL,
2017-10-30 13:57:02 -06:00
'key': save_temp(certificado.key_enc, 'w'),
2017-10-10 18:49:05 -05:00
'pass': _get_md5(certificado.rfc)
}
args = '"{xsltproc}" "{xslt}" "{xml}" | ' \
2017-10-30 21:56:53 -06:00
'"{openssl}" dgst -sha256 -sign "{key}" -passin pass:"{pass}" | ' \
2017-10-10 18:49:05 -05:00
'"{openssl}" enc -base64 -A'.format(**data)
sello = _call(args)
_kill(data['xml'])
_kill(data['key'])
return cfdi.add_sello(sello)
2017-10-25 22:26:18 -05:00
def timbra_xml(xml, auth):
2017-10-10 18:49:05 -05:00
from .pac import Finkok as PAC
2017-10-25 22:26:18 -05:00
if DEBUG:
auth = {}
else:
if not auth:
msg = 'Sin datos para timbrar'
result = {'ok': True, 'error': msg}
return result
2017-10-10 18:49:05 -05:00
result = {'ok': True, 'error': ''}
2017-10-25 22:26:18 -05:00
pac = PAC(auth)
2017-10-10 18:49:05 -05:00
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
2017-10-28 23:37:08 -05:00
def get_sat(xml):
from .pac import get_status_sat
return get_status_sat(xml)
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):
2017-11-05 19:53:27 -06:00
self._es_pre = False
2017-10-15 02:30:55 -05:00
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)
2017-10-31 20:08:18 -06:00
else:
row = cell_2.getCellAddress().Row + 1
self._sheet.getRows().insertByIndex(row, 1)
if cell_1:
self._copy_cell(cell_1)
cell_1 = self._set_cell(v=key, cell=cell_1)
if cell_3:
self._copy_cell(cell_3)
cell_3 = self._set_cell(v=unidad, cell=cell_3)
self._copy_cell(cell_2)
self._copy_cell(cell_4)
self._copy_cell(cell_5)
self._copy_cell(cell_6)
cell_2 = self._set_cell(v=description, cell=cell_2)
cell_4 = self._set_cell(v=cantidad, cell=cell_4, value=True)
cell_5 = self._set_cell(v=valor_unitario, cell=cell_5, value=True)
cell_6 = self._set_cell(v=importe, cell=cell_6, value=True)
2017-10-15 17:20:20 -05:00
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}
2017-11-16 01:17:22 -06:00
#~ print (data['descuento'])
2017-10-15 17:20:20 -05:00
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):
2017-11-05 19:53:27 -06:00
if self._es_pre:
return
2017-10-15 02:30:55 -05:00
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()
2017-11-22 00:46:23 -06:00
s.Width = 4150
2017-10-15 17:20:20 -05:00
s.Height = 4500
image.setSize(s)
image.Anchor = self._set_cell('{timbre.cbb}')
2017-10-15 02:30:55 -05:00
return
2017-11-25 20:26:15 -06:00
def _donataria(self, data):
if not data:
return
for k, v in data.items():
self._set_cell('{donataria.%s}' % k, v)
return
2017-11-26 00:15:14 -06:00
def _ine(self, data):
if not data:
return
for k, v in data.items():
self._set_cell('{ine.%s}' % k, v)
return
2017-10-15 02:30:55 -05:00
def _render(self, data):
self._set_search()
2017-11-05 19:53:27 -06:00
self._es_pre = data.pop('es_pre', False)
2017-10-15 02:30:55 -05:00
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-11-25 20:26:15 -06:00
self._donataria(data['donataria'])
2017-11-26 00:15:14 -06:00
self._ine(data['ine'])
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)
2017-11-25 20:26:15 -06:00
def to_pdf(data, emisor_rfc):
2017-10-25 19:46:13 -05:00
rfc = data['emisor']['rfc']
2017-11-25 20:26:15 -06:00
if DEBUG:
rfc = emisor_rfc
2017-10-25 19:46:13 -05:00
version = data['comprobante']['version']
if APP_LIBO:
app = LIBO()
if app.is_running:
2017-11-25 20:26:15 -06:00
donativo = ''
if data['donativo']:
donativo = '_donativo'
name = '{}_{}{}.ods'.format(rfc.lower(), version, donativo)
2017-10-25 19:46:13 -05:00
path = get_template_ods(name)
if path:
return app.pdf(path, data)
name = '{}_{}.json'.format(rfc, version)
custom_styles = get_custom_styles(name)
2017-10-23 00:45:41 -05:00
path = get_path_temp()
pdf = TemplateInvoice(path)
2017-10-25 19:46:13 -05:00
pdf.custom_styles = custom_styles
2017-10-23 00:45:41 -05:00
pdf.data = data
pdf.render()
return read_file(path)
2017-10-15 02:30:55 -05:00
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
}
2017-11-09 23:51:54 -06:00
return NumLet(value, monedas.get(moneda, moneda)).letras
2017-10-15 02:30:55 -05:00
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-11-11 15:03:20 -06:00
def _get_relacionados(doc, version):
node = doc.find('{}CfdiRelacionados'.format(PRE[version]))
if node is None:
return ''
uuids = ['UUID: {}'.format(n.attrib['UUID']) for n in node.getchildren()]
return '\n'.join(uuids)
def _comprobante(doc, options):
data = CaseInsensitiveDict(doc.attrib.copy())
2017-10-15 02:30:55 -05:00
del data['certificado']
2017-10-15 17:20:20 -05:00
2017-12-01 09:55:55 -06:00
data['seriefolio'] = '{} - {}'.format(data['serie'], data['folio'])
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. de Expedición: {}'.format(data['lugarexpedicion'])
2017-10-15 17:20:20 -05:00
data['metododepago'] = options['metododepago']
data['formadepago'] = options['formadepago']
2017-11-06 19:55:58 -06:00
if 'condicionesdepago' in data:
data['condicionesdepago'] = \
'Condiciones de pago: {}'.format(data['condicionesdepago'])
2017-10-15 17:20:20 -05:00
data['moneda'] = options['moneda']
2017-11-11 15:03:20 -06:00
data['tiporelacion'] = options.get('tiporelacion', '')
data['relacionados'] = _get_relacionados(doc, data['version'])
2017-11-11 13:42:51 -06:00
else:
fields = {
'formaDePago': 'Forma de Pago: {}\n',
'metodoDePago': 'Método de pago: {}\n',
'condicionesDePago': 'Condiciones de Pago: {}\n',
'NumCtaPago': 'Número de Cuenta de Pago: {}\n',
'Moneda': 'Moneda: {}\n',
'TipoCambio': 'Tipo de Cambio: {}',
}
datos = ''
for k, v in fields.items():
if k in data:
datos += v.format(data[k])
data['datos'] = datos
2017-12-06 16:38:42 -06:00
data['hora'] = data['fecha'].split('T')[1]
2017-11-11 13:42:51 -06:00
fecha = parser.parse(data['fecha'])
try:
locale.setlocale(locale.LC_TIME, "es_MX.UTF-8")
except:
pass
data['fechaformato'] = fecha.strftime('%A, %d de %B de %Y')
2017-10-15 17:20:20 -05:00
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
float(data['tipocambio']))
2017-11-11 13:42:51 -06:00
2017-10-15 02:30:55 -05:00
return data
2017-10-15 17:20:20 -05:00
def _emisor(doc, version, values):
2017-11-09 23:51:54 -06:00
emisor = doc.find('{}Emisor'.format(PRE[version]))
data = CaseInsensitiveDict(emisor.attrib.copy())
node = emisor.find('{}DomicilioFiscal'.format(PRE[version]))
2017-10-15 17:20:20 -05:00
if not node is None:
2017-10-23 00:45:41 -05:00
data.update(CaseInsensitiveDict(node.attrib.copy()))
2017-11-09 23:51:54 -06:00
if version == '3.2':
node = emisor.find('{}RegimenFiscal'.format(PRE[version]))
if not node is None:
data['regimenfiscal'] = node.attrib['Regimen']
2017-12-06 16:38:42 -06:00
data['regimen'] = node.attrib['Regimen']
2017-11-09 23:51:54 -06:00
else:
data['regimenfiscal'] = values['regimenfiscal']
2017-10-24 00:03:07 -05:00
path = _join(PATH_MEDIA, 'logos', '{}.png'.format(data['rfc'].lower()))
if is_file(path):
data['logo'] = path
2017-10-15 17:20:20 -05:00
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())
2017-11-09 23:51:54 -06:00
if version == '3.2':
return data
2017-10-15 17:20:20 -05:00
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'])
2017-10-24 00:03:07 -05:00
values['unidad'] = '({})\n{}'.format(
2017-10-15 17:20:20 -05:00
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',
2017-10-16 23:09:26 -05:00
'003': 'IEPS',
2017-10-15 17:20:20 -05:00
}
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'])))
2017-11-29 23:57:31 -06:00
node = doc.find('{}Complemento/{}ImpuestosLocales'.format(
PRE[version], PRE['LOCALES']))
if node is not None:
for otro in list(node):
if otro.tag == '{}RetencionesLocales'.format(PRE['LOCALES']):
tipo = 'Retención '
name = 'ImpLocRetenido'
tasa = 'TasadeRetencion'
else:
tipo = 'Traslado '
name = 'ImpLocTrasladado'
tasa = 'TasadeTraslado'
title = '{} {} {}%'.format(
tipo, otro.attrib[name], otro.attrib[tasa])
importe = float(otro.attrib['Importe'])
taxlocales.append((title, importe))
2017-10-15 17:20:20 -05:00
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-11-25 20:32:36 -06:00
def _donataria(doc, version, fechadof):
2017-11-25 20:26:15 -06:00
node = doc.find('{}Complemento/{}Donatarias'.format(
PRE[version], PRE['DONATARIA']))
if node is None:
return {}
data = CaseInsensitiveDict(node.attrib.copy())
2017-11-25 20:32:36 -06:00
data['fechadof'] = fechadof
2017-11-25 20:26:15 -06:00
return data
2017-11-26 00:15:14 -06:00
def _ine(doc, version):
node = doc.find('{}Complemento/{}INE'.format(PRE[version], PRE['INE']))
if node is None:
return {}
values = (
('TipoComite', 'Tipo de Comite: {}'),
('TipoProceso', 'Tipo de Proceso: {}'),
('IdContabilidad', 'ID de Contabilidad: {}'),
)
data = CaseInsensitiveDict(node.attrib.copy())
for k, v in values:
data[k] = v.format(data[k])
return data
2017-10-25 19:46:13 -05:00
def get_data_from_xml(invoice, values):
2017-11-25 20:26:15 -06:00
data = {'cancelada': invoice.cancelada, 'donativo': invoice.donativo}
2017-10-15 02:30:55 -05:00
doc = parse_xml(invoice.xml)
2017-11-11 15:03:20 -06:00
data['comprobante'] = _comprobante(doc, 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)
2017-11-25 20:32:36 -06:00
data['donataria'] = _donataria(doc, version, values['fechadof'])
2017-11-26 00:15:14 -06:00
data['ine'] = _ine(doc, version)
2017-10-15 17:20:20 -05:00
options = {
'rfc_emisor': data['emisor']['rfc'],
'rfc_receptor': data['receptor']['rfc'],
'total': data['comprobante']['total'],
}
data['timbre'] = _timbre(doc, version, options)
2017-10-25 19:46:13 -05:00
del data['timbre']['version']
2017-10-24 00:03:07 -05:00
data['comprobante'].update(data['timbre'])
2017-10-15 02:30:55 -05:00
2017-10-25 19:46:13 -05:00
return data
2017-10-15 18:57:25 -05:00
def to_zip(*files):
zip_buffer = BytesIO()
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file:
for data, file_name in files:
zip_file.writestr(file_name, data)
return zip_buffer.getvalue()
2017-10-16 00:02:51 -05:00
def make_fields(xml):
doc = ET.fromstring(xml)
data = CaseInsensitiveDict(doc.attrib.copy())
data.pop('certificado')
data.pop('sello')
version = data['version']
receptor = doc.find('{}Receptor'.format(PRE[version]))
receptor = CaseInsensitiveDict(receptor.attrib.copy())
data['receptor_nombre'] = receptor['nombre']
data['receptor_rfc'] = receptor['rfc']
data = {k.lower(): v for k, v in data.items()}
return data
def make_info_mail(data, fields):
return data.format(**fields).replace('\n', '<br/>')
def send_mail(data):
msg = ''
server = SendMail(data['server'])
is_connect = server.is_connect
if is_connect:
msg = server.send(data['options'])
else:
msg = server.error
server.close()
return {'ok': is_connect, 'msg': msg}
2017-10-16 23:09:26 -05:00
def get_path_info(path):
path, filename = os.path.split(path)
name, extension = os.path.splitext(filename)
return (path, filename, name, extension)
2017-10-23 00:45:41 -05:00
def get_path_temp():
return tempfile.mkstemp()[1]
2017-10-28 22:21:39 -05:00
def get_date(value, next_day=False):
d = parser.parse(value)
if next_day:
return d + datetime.timedelta(days=1)
return d
2017-11-03 14:09:34 -06:00
def upload_file(rfc, opt, file_obj):
if opt == 'emisorlogo':
tmp = file_obj.filename.split('.')
name = '{}.{}'.format(rfc.lower(), tmp[-1].lower())
path = _join(PATH_MEDIA, 'logos', name)
2017-11-25 00:17:46 -06:00
elif opt == 'txt_plantilla_factura_32':
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
if ext != 'ods':
msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS'
return {'status': 'server', 'name': msg, 'ok': False}
name = '{}_3.2.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'templates', name)
2017-11-03 20:05:19 -06:00
elif opt == 'txt_plantilla_factura_33':
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
if ext != 'ods':
msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS'
return {'status': 'server', 'name': msg, 'ok': False}
name = '{}_3.3.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'templates', name)
2017-11-25 00:17:46 -06:00
elif opt == 'txt_plantilla_factura_33j':
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
if ext != 'json':
msg = 'Extensión de archivo incorrecta, selecciona un archivo JSON'
return {'status': 'server', 'name': msg, 'ok': False}
name = '{}_3.3.json'.format(rfc.lower())
path = _join(PATH_MEDIA, 'templates', name)
elif opt == 'txt_plantilla_donataria':
2017-11-11 11:40:55 -06:00
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
if ext != 'ods':
msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS'
return {'status': 'server', 'name': msg, 'ok': False}
2017-11-25 20:26:15 -06:00
name = '{}_3.3_donativo.ods'.format(rfc.lower())
2017-11-11 11:40:55 -06:00
path = _join(PATH_MEDIA, 'templates', name)
2017-11-29 23:57:31 -06:00
elif opt == 'bdfl':
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
if ext != 'sqlite':
msg = 'Extensión de archivo incorrecta, selecciona un archivo SQLite'
return {'status': 'server', 'name': msg, 'ok': False}
name = '{}.sqlite'.format(rfc.lower())
path = _join('/tmp', name)
2017-11-03 20:05:19 -06:00
if save_file(path, file_obj.file.read()):
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
2017-11-03 14:09:34 -06:00
2017-11-24 10:47:15 -06:00
return {'status': 'error', 'ok': False}
2017-11-03 14:09:34 -06:00
2017-10-29 16:53:10 -06:00
def cancel_cfdi(uuid, pk12, rfc, auth):
from .pac import Finkok as PAC
template = read_file(TEMPLATE_CANCEL, 'r')
data = {
'rfc': rfc,
'fecha': datetime.datetime.now().isoformat()[:19],
'uuid': str(uuid).upper(),
}
template = template.format(**data)
data = {
'xmlsec': PATH_XMLSEC,
2017-10-30 13:57:02 -06:00
'pk12': save_temp(pk12),
2017-10-29 16:53:10 -06:00
'pass': _get_md5(rfc),
2017-10-30 13:57:02 -06:00
'template': save_temp(template, 'w'),
2017-10-29 16:53:10 -06:00
}
args = '"{xmlsec}" --sign --pkcs12 "{pk12}" --pwd {pass} ' \
'"{template}"'.format(**data)
xml_sign = _call(args)
if DEBUG:
auth = {}
else:
if not auth:
msg = 'Sin datos para cancelar'
result = {'ok': False, 'error': msg}
return result
msg = 'Factura cancelada correctamente'
data = {'ok': True, 'msg': msg, 'row': {'estatus': 'Cancelada'}}
pac = PAC(auth)
result = pac.cancel_signature(xml_sign)
if result:
codes = {None: '',
'Could not get UUID Text': 'UUID no encontrado'}
if not result['CodEstatus'] is None:
data['ok'] = False
data['msg'] = codes.get(result['CodEstatus'], result['CodEstatus'])
else:
data['ok'] = False
data['msg'] = pac.error
return data, result
2017-11-06 22:21:14 -06:00
def run_in_thread(fn):
def run(*k, **kw):
t = threading.Thread(target=fn, args=k, kwargs=kw)
t.start()
return t
return run
def get_bool(value):
if not value:
return False
if value == '1':
return True
return False
2017-11-21 00:48:51 -06:00
def get_float(value):
return round(float(value), DECIMALES)
2017-12-03 00:09:44 -06:00
def crear_rol(user, contra=''):
if not contra:
contra = user
args = 'psql -U postgres -c "CREATE ROLE {} WITH LOGIN ENCRYPTED ' \
'PASSWORD \'{}\';"'.format(user, contra)
try:
result = _call(args)
if result == 'CREATE ROLE\n':
return True
except Exception as e:
log.info(e)
return False
def crear_db(nombre):
args = 'psql -U postgres -c "CREATE DATABASE {0} WITH ' \
'OWNER {0};"'.format(nombre)
try:
result = _call(args)
print (result)
if result == 'CREATE DATABASE\n':
return True
except Exception as e:
log.info(e)
return False
2017-10-16 23:09:26 -05:00
class ImportFacturaLibre(object):
def __init__(self, path, rfc):
self._rfc = rfc
2017-10-16 23:09:26 -05:00
self._con = None
self._cursor = None
2017-11-15 19:29:51 -06:00
self._error = ''
2017-10-16 23:09:26 -05:00
self._is_connect = self._connect(path)
2017-11-09 23:22:36 -06:00
self._clientes = []
self._clientes_rfc = []
@property
def error(self):
return self._error
2017-10-16 23:09:26 -05:00
@property
def is_connect(self):
return self._is_connect
def _validate_rfc(self):
sql = "SELECT rfc FROM emisor LIMIT 1"
self._cursor.execute(sql)
obj = self._cursor.fetchone()
if obj is None:
self._error = 'No se encontró al emisor: {}'.format(self._rfc)
return False
2017-11-29 23:57:31 -06:00
if not DEBUG:
if obj['rfc'] != self._rfc:
self._error = 'Los datos no corresponden al RFC: {}'.format(self._rfc)
return False
return True
2017-10-16 23:09:26 -05:00
def _connect(self, path):
try:
self._con = sqlite3.connect(path)
self._con.row_factory = sqlite3.Row
self._cursor = self._con.cursor()
return self._validate_rfc()
2017-10-16 23:09:26 -05:00
except Exception as e:
log.error(e)
self._error = 'No se pudo conectar a la base de datos'
2017-10-16 23:09:26 -05:00
return False
def close(self):
try:
self._cursor.close()
self._con.close()
except:
pass
return
def import_data(self):
data = {}
tables = (
('receptores', 'Socios'),
2017-11-09 23:22:36 -06:00
('cfdfacturas', 'Facturas'),
2017-11-15 19:29:51 -06:00
('categorias', 'Categorias'),
2017-10-16 23:09:26 -05:00
)
for source, target in tables:
data[target] = self._get_table(source)
2017-11-09 23:22:36 -06:00
data['Socios'] += self._clientes
2017-10-16 23:09:26 -05:00
return data
def _get_table(self, table):
return getattr(self, '_{}'.format(table))()
2017-11-15 19:29:51 -06:00
def import_productos(self):
sql = "SELECT * FROM productos"
self._cursor.execute(sql)
rows = self._cursor.fetchall()
fields = (
('id_categoria', 'categoria'),
('noIdentificacion', 'clave'),
('descripcion', 'descripcion'),
('unidad', 'unidad'),
('valorUnitario', 'valor_unitario'),
('existencia', 'existencia'),
('inventario', 'inventario'),
('codigobarras', 'codigo_barras'),
('CuentaPredial', 'cuenta_predial'),
('precio_compra', 'ultimo_precio'),
('minimo', 'minimo'),
)
data = []
sql = """
SELECT nombre, tasa, tipo
FROM impuestos, productos, productosimpuestos
WHERE productos.id=productosimpuestos.id_producto
AND productosimpuestos.id_impuesto=impuestos.id
AND productos.id = ?
"""
for row in rows:
new = {t: row[s] for s, t in fields}
new['descripcion'] = ' '.join(new['descripcion'].split())
new['clave_sat'] = DEFAULT_SAT_PRODUCTO
self._cursor.execute(sql, (row['id'],))
impuestos = self._cursor.fetchall()
new['impuestos'] = tuple(impuestos)
data.append(new)
return data
def _categorias(self):
sql = "SELECT * FROM categorias ORDER BY id_padre"
2017-11-15 19:29:51 -06:00
self._cursor.execute(sql)
rows = self._cursor.fetchall()
fields = (
('id', 'id'),
2017-11-15 19:29:51 -06:00
('categoria', 'categoria'),
('id_padre', 'padre'),
)
data = []
for row in rows:
new = {t: row[s] for s, t in fields}
if new['padre'] == 0:
new['padre'] = None
data.append(new)
return data
2017-11-09 23:22:36 -06:00
def _get_cliente(self, invoice):
sql = "SELECT rfc, nombre FROM receptores WHERE id=?"
self._cursor.execute(sql, [invoice['id_cliente']])
obj = self._cursor.fetchone()
if not obj is None:
data = {
'rfc': obj['rfc'],
'slug': to_slug(obj['nombre']),
}
return data
if not invoice['xml']:
return {}
doc = parse_xml(invoice['xml'])
version = doc.attrib['version']
node = doc.find('{}Receptor'.format(PRE[version]))
rfc = node.attrib['rfc']
nombre = node.attrib['nombre']
tipo_persona = 1
if rfc == 'XEXX010101000':
tipo_persona = 4
elif rfc == 'XAXX010101000':
tipo_persona = 3
elif len(rfc) == 12:
tipo_persona = 2
data = {
'tipo_persona': tipo_persona,
'rfc': rfc,
'nombre': nombre,
'slug': to_slug(nombre),
'es_cliente': True,
'es_activo': False,
}
if not rfc in self._clientes_rfc:
self._clientes_rfc.append(rfc)
self._clientes.append(data)
data = {
'rfc': data['rfc'],
'slug': data['slug'],
}
return data
def _get_detalles(self, id):
sql = "SELECT * FROM cfddetalle WHERE id_cfd=?"
self._cursor.execute(sql, [id])
rows = self._cursor.fetchall()
fields = (
('categoria', 'categoria'),
('cantidad', 'cantidad'),
('unidad', 'unidad'),
('noIdentificacion', 'clave'),
('descripcion', 'descripcion'),
('valorUnitario', 'valor_unitario'),
('importe', 'importe'),
('numero', 'pedimento'),
('fecha', 'fecha_pedimento'),
('aduana', 'aduana'),
('CuentaPredial', 'cuenta_predial'),
('alumno', 'alumno'),
('curp', 'curp'),
('nivel', 'nivel'),
('autorizacion', 'autorizacion'),
)
data = []
for row in rows:
new = {t: row[s] for s, t in fields}
data.append(new)
return data
def _get_impuestos(self, id):
sql = "SELECT * FROM cfdimpuestos WHERE id_cfd=?"
self._cursor.execute(sql, [id])
rows = self._cursor.fetchall()
tasas = {
'16': 0.16,
'11': 0.11,
2017-11-09 23:22:36 -06:00
'-10': 0.10,
'0': 0.0,
'-2/3': 0.666667,
2017-11-30 21:40:49 -06:00
'-0.5': 0.005,
2017-11-09 23:22:36 -06:00
}
data = []
for row in rows:
filtro = {
'name': row['nombre'],
'tasa': tasas[row['tasa']],
'tipo': row['tipo'][0],
}
new = {
'importe': row['importe'],
'filtro': filtro
}
data.append(new)
return data
def _cfdfacturas(self):
sql = "SELECT * FROM cfdfacturas"
self._cursor.execute(sql)
rows = self._cursor.fetchall()
fields = (
('version', 'version'),
('serie', 'serie'),
('folio', 'folio'),
('fecha', 'fecha'),
('fecha_timbrado', 'fecha_timbrado'),
('formaDePago', 'forma_pago'),
('condicionesDePago', 'condiciones_pago'),
('subTotal', 'subtotal'),
('descuento', 'descuento'),
('TipoCambio', 'tipo_cambio'),
('Moneda', 'moneda'),
('total', 'total'),
('tipoDeComprobante', 'tipo_comprobante'),
('metodoDePago', 'metodo_pago'),
('LugarExpedicion', 'lugar_expedicion'),
('totalImpuestosRetenidos', 'total_retenciones'),
('totalImpuestosTrasladados', 'total_traslados'),
('xml', 'xml'),
('id_cliente', 'cliente'),
('notas', 'notas'),
('uuid', 'uuid'),
('donativo', 'donativo'),
('estatus', 'estatus'),
('regimen', 'regimen_fiscal'),
('xml_acuse', 'acuse'),
)
data = []
for row in rows:
new = {t: row[s] for s, t in fields}
2017-11-16 01:17:22 -06:00
if not new['uuid']:
new['uuid'] = None
2017-11-16 22:49:17 -06:00
if new['xml'] is None:
new['xml'] = ''
2017-11-09 23:22:36 -06:00
if row['estatus'] == 'Pagada':
new['pagada'] = True
elif row['estatus'] == 'Cancelada':
new['cancelada'] = True
new['total_mn'] = round(row['TipoCambio'] * row['total'], 2)
new['detalles'] = self._get_detalles(row['id'])
new['impuestos'] = self._get_impuestos(row['id'])
new['cliente'] = self._get_cliente(row)
data.append(new)
return data
2017-10-16 23:09:26 -05:00
def _receptores(self):
sql = "SELECT * FROM receptores"
self._cursor.execute(sql)
rows = self._cursor.fetchall()
2017-11-09 23:22:36 -06:00
2017-10-16 23:09:26 -05:00
fields = (
('rfc', 'rfc'),
('nombre', 'nombre'),
('calle', 'calle'),
('noExterior', 'no_exterior'),
('noInterior', 'no_interior'),
('colonia', 'colonia'),
('municipio', 'municipio'),
('estado', 'estado'),
('pais', 'pais'),
('codigoPostal', 'codigo_postal'),
('extranjero', 'es_extranjero'),
('activo', 'es_activo'),
('fechaalta', 'fecha_alta'),
('notas', 'notas'),
('cuentaCliente', 'cuenta_cliente'),
('cuentaProveedor', 'cuenta_proveedor'),
('saldoCliente', 'saldo_cliente'),
('saldoProveedor', 'saldo_proveedor'),
('esCliente', 'es_cliente'),
('esProveedor', 'es_proveedor'),
)
data = []
2017-10-17 12:45:49 -05:00
sql1 = "SELECT correo FROM correos WHERE id_cliente=?"
sql2 = "SELECT telefono FROM telefonos WHERE id_cliente=?"
2017-10-16 23:09:26 -05:00
for row in rows:
new = {t: row[s] for s, t in fields}
new['slug'] = to_slug(new['nombre'])
if new['es_extranjero']:
new['tipo_persona'] = 4
elif new['rfc'] == 'XAXX010101000':
new['tipo_persona'] = 3
elif len(new['rfc']) == 12:
new['tipo_persona'] = 2
2017-10-17 12:45:49 -05:00
2017-11-09 23:22:36 -06:00
self._cursor.execute(sql1, (row['id'],))
2017-10-17 12:45:49 -05:00
tmp = self._cursor.fetchall()
if tmp:
new['correo_facturas'] = ', '.join([r[0] for r in tmp])
2017-11-09 23:22:36 -06:00
self._cursor.execute(sql2, (row['id'],))
2017-10-17 12:45:49 -05:00
tmp = self._cursor.fetchall()
if tmp:
new['telefonos'] = ', '.join([r[0] for r in tmp])
2017-10-16 23:09:26 -05:00
data.append(new)
return data