Merge branch 'develop'

402f54f Sincrionizacion SeaFile partícular
37aea44 Sincrionizacion con SeaFile general
f2c9de3 Backups in thread
f99a0da Generar backups de sqlite
50a8c27 Generar backups de base de datos
6644eef Fix - Issue 48
de675a0 Fix - Mostrar cuenta prefial en prefactura
38f2b5a Fix - Issue 47
bb14b54 Fix - Issue 46
This commit is contained in:
Mauricio Baeza 2017-12-10 14:40:42 -06:00
commit efec0c1396
6 changed files with 323 additions and 15 deletions

View File

@ -11,3 +11,15 @@ DEFAULT_PASSWORD = 'blades3.3'
#~ Establece una ruta accesible para el servidor web #~ Establece una ruta accesible para el servidor web
LOG_PATH = '/srv/empresa/logs/empresalibre.log' LOG_PATH = '/srv/empresa/logs/empresalibre.log'
# ~ Establece los valores para sincronizar los backups de la base de datos
# ~ por ejemplo
# ~ SEAFILE_SERVER = {
# ~ 'URL': 'https://tu_servidor_seafile',
# ~ 'USER': 'tu_usuario',
# ~ 'PASS': 'tu_contraseña',
# ~ 'REPO': 'id_repo',
# ~ }
SEAFILE_SERVER = {}

View File

@ -12,6 +12,9 @@ from email.mime.text import MIMEText
from email import encoders from email import encoders
from email.utils import formatdate from email.utils import formatdate
import os
import requests
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
@ -724,3 +727,200 @@ class ReportTemplate(BaseDocTemplate):
return return
class SeaFileAPI(object):
FILE_DOES_NOT_EXIST = 441
def __init__(self, url, username, password):
self._url = url
self._headers = self._get_auth(username, password)
@property
def is_connect(self):
return bool(self._headers)
def _open(self, path):
return open(path, 'rb')
def _get_auth(self, username, password):
url = self._url + 'auth-token/'
data = {
'username': username,
'password': password,
}
resp = requests.post(url, data=data)
if resp.status_code != requests.codes.ok:
msg = 'Token Error: {}'.format(resp.status_code)
print (msg)
return {}
headers = {'Authorization': 'Token {}'.format(resp.json()['token'])}
return headers
def _get_upload_link(self, repo_id):
if not self._headers:
return ''
url = '{}repos/{}/upload-link/'.format(self._url, repo_id)
resp = requests.get(url, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Error: {}'.format(resp.status_code)
print (msg)
return ''
return resp.json()
def _get_update_link(self, repo_id):
if not self._headers:
return ''
url = '{}repos/{}/update-link/'.format(self._url, repo_id)
data = {'p': '/'}
resp = requests.get(url, data=data, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Error: {}'.format(resp.status_code)
print (msg)
return ''
return resp.json()
def _decrypt(self, repo_id, password):
if not self._headers:
return False
url = '{}repos/{}/'.format(self._url, repo_id)
data = {'password': password}
resp = requests.post(url, data=data, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Error: {}'.format(resp.status_code)
print (msg)
return False
return True
def upload_file(self, path_file, repo_id, relative_path, password=''):
if not self._headers:
return False
if password:
if not self._decrypt(repo_id, password):
return False
upload_link = self._get_upload_link(repo_id)
data = {
'filename': path_file,
'parent_dir': relative_path,
'relative_path': '',
}
files = {'file': self._open(path_file)}
resp = requests.post(
upload_link, data=data, files=files, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Upload Code: {}\n{}'.format(resp.status_code, resp.text)
print (msg)
return False
return True
def _info_path(self, path):
path, filename = os.path.split(path)
return path, filename
def list_directory(self, repo_id):
if not self._headers:
return False
url = '{}repos/{}/dir/'.format(self._url, repo_id)
params = {'p': '/', 't': 'd', 'recursive': '1'}
try:
resp = requests.get(url, params=params, headers=self._headers)
return resp.json()
except Exception as e:
return False
def create_dir(self, repo_id, name):
url = '{}repos/{}/dir/'.format(self._url, repo_id)
data = {'operation': 'mkdir'}
params = {'p': name}
resp = requests.post(url, data=data, headers=self._headers, params=params)
if resp.status_code != requests.codes.created:
msg = 'Create Dir Error: {}'.format(resp.status_code)
print (msg)
return False
return True
def _validate_directory(self, repo_id, target_file):
if target_file == '/':
return True
names = target_file.split('/')[:-1]
directories = self.list_directory(repo_id)
if isinstance(directories, bool):
return False
exists = False
parent_dir = '/'
name_dir = ''
for name in names:
name_dir += '/' + name
for directory in directories:
if name == directory['name'] and parent_dir == directory['parent_dir']:
exists = True
break
if exists:
exists = False
else:
self.create_dir(repo_id, name_dir)
if parent_dir == '/':
parent_dir += name
else:
parent_dir += '/' + name
return True
def update_file(self, path_file, repo_id, target_file, password, create=True):
if not self._headers:
return False
if not self._validate_directory(repo_id, target_file):
return False
if password:
if not self._decrypt(repo_id, password):
return False
update_link = self._get_update_link(repo_id)
_, filename = self._info_path(path_file)
files = {
'file': (filename, self._open(path_file)),
'filename': (None, filename),
'target_file': (None, '{}{}'.format(target_file, filename))
}
resp = requests.post(update_link, files=files, headers=self._headers)
if resp.status_code != requests.codes.ok:
msg = 'Update Code: {}\n{}'.format(resp.status_code, resp.text)
print (msg)
if resp.status_code == self.FILE_DOES_NOT_EXIST and create:
relative_path = '/'
if target_file != '/':
relative_path += target_file
return self.upload_file(path_file, repo_id, relative_path, password)
else:
return False
return True
def ping(self):
url = self._url + 'ping/'
resp = requests.get(url)
return resp.json()
def auth_ping(self):
url = self._url + 'auth/ping/'
resp = requests.get(url, headers=self._headers)
return resp.json()

View File

@ -33,18 +33,13 @@ except:
import pyqrcode import pyqrcode
from dateutil import parser from dateutil import parser
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \
SeaFileAPI
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \ from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \ PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES
from settings import SEAFILE_SERVER
#~ def _get_hash(password):
#~ return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
#~ def validate_password(hashed, password):
#~ return bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode()
def _call(args): def _call(args):
@ -1389,6 +1384,92 @@ def crear_db(nombre):
return False return False
def _to_seafile(path_db, data):
# ~ if DEBUG:
# ~ return
_, filename = os.path.split(path_db)
if SEAFILE_SERVER:
msg = '\tSincronizando backup general...'
log.info(msg)
seafile = SeaFileAPI(
SEAFILE_SERVER['URL'],
SEAFILE_SERVER['USER'],
SEAFILE_SERVER['PASS'])
if seafile.is_connect:
msg = '\tSincronizando: {} '.format(filename)
log.info(msg)
seafile.update_file(
path_db, SEAFILE_SERVER['REPO'], '/', SEAFILE_SERVER['PASS'])
msg = '\tRespaldo general de {} sincronizado'.format(filename)
log.info(msg)
msg = '\tSin datos para sincronización particular de {}'.format(filename)
if len(data) < 2:
log.info(msg)
return
if not data[0] or not data[1] or not data[2]:
log.info(msg)
return
msg = '\tSincronizando backup particular...'
log.info(msg)
seafile = SeaFileAPI(SEAFILE_SERVER['URL'], data[0], data[1])
if seafile.is_connect:
msg = '\t\tSincronizando: {} '.format(filename)
log.info(msg)
seafile.update_file(path_db, data[2], 'Base de datos/', data[1])
msg = '\t\tRespaldo partícular de {} sincronizado'.format(filename)
log.info(msg)
return
@run_in_thread
def _backup_and_sync(rfc, data):
msg = 'Generando backup de: {}'.format(rfc)
log.info(msg)
sql = 'select correo_timbrado, token_timbrado, token_soporte from emisor;'
path_bk = _join(PATH_MEDIA, 'tmp', '{}.bk'.format(rfc.lower()))
if data['type'] == 'postgres':
args = 'pg_dump -U postgres -Fc {} > "{}"'.format(
data['name'], path_bk)
sql = 'psql -U postgres -d {} -Atc "{}"'.format(data['name'], sql)
elif data['type'] == 'sqlite':
args = 'gzip -c "{}" > "{}"'.format(data['name'], path_bk)
sql = 'sqlite3 "{}" "{}"'.format(data['name'], sql)
try:
result = _call(args)
msg = '\tBackup generado de {}'.format(rfc)
log.info(msg)
result = _call(sql).strip().split('|')
_to_seafile(path_bk, result)
except Exception as e:
log.info(e)
return
def backup_dbs():
con = sqlite3.connect(COMPANIES)
cursor = con.cursor()
sql = "SELECT * FROM names"
cursor.execute(sql)
rows = cursor.fetchall()
if rows is None:
return
cursor.close()
con.close()
for rfc, data in rows:
args = loads(data)
_backup_and_sync(rfc, args)
return
class ImportFacturaLibre(object): class ImportFacturaLibre(object):
def __init__(self, path, rfc): def __init__(self, path, rfc):
@ -1603,7 +1684,7 @@ class ImportFacturaLibre(object):
'11': 0.11, '11': 0.11,
'-10': 0.10, '-10': 0.10,
'0': 0.0, '0': 0.0,
'-2/3': 0.666667, '-2/3': 0.106667,
'-0.5': 0.005, '-0.5': 0.005,
} }

View File

@ -1719,7 +1719,6 @@ class Productos(BaseModel):
@classmethod @classmethod
def get_by(cls, values): def get_by(cls, values):
# ~ id = int(values.get('id', 0))
clave = values.get('id', '') clave = values.get('id', '')
if clave: if clave:
row = (Productos row = (Productos
@ -1732,7 +1731,8 @@ class Productos(BaseModel):
Productos.valor_unitario, Productos.valor_unitario,
Productos.descuento) Productos.descuento)
.join(SATUnidades).switch(Productos) .join(SATUnidades).switch(Productos)
.where(Productos.clave==clave).dicts()) .where((Productos.id==clave) | (Productos.clave==clave))
.dicts())
if len(row): if len(row):
id = row[0]['id'] id = row[0]['id']
model_pt = Productos.impuestos.get_through_model() model_pt = Productos.impuestos.get_through_model()
@ -3003,6 +3003,7 @@ class PreFacturas(BaseModel):
product['unidad'] = p.unidad.key product['unidad'] = p.unidad.key
product['clave'] = p.clave product['clave'] = p.clave
product['clave_sat'] = p.clave_sat product['clave_sat'] = p.clave_sat
product['cuenta_predial'] = p.cuenta_predial
product['factura'] = invoice.id product['factura'] = invoice.id
product['producto'] = id_product product['producto'] = id_product
@ -3057,8 +3058,8 @@ class PreFacturas(BaseModel):
invoice_tax = { invoice_tax = {
'factura': invoice.id, 'factura': invoice.id,
'impuesto': tax['id'], 'impuesto': tax.id,
'base': tax['importe'], 'base': tax.importe,
'importe': import_tax, 'importe': import_tax,
} }
PreFacturasImpuestos.create(**invoice_tax) PreFacturasImpuestos.create(**invoice_tax)
@ -3276,7 +3277,12 @@ class PreFacturasDetalle(BaseModel):
producto = {} producto = {}
producto['noidentificacion'] = '{}\n(SAT {})'.format( producto['noidentificacion'] = '{}\n(SAT {})'.format(
p.producto.clave, p.producto.clave_sat) p.producto.clave, p.producto.clave_sat)
producto['descripcion'] = p.descripcion producto['descripcion'] = p.descripcion
if p.cuenta_predial:
info = '\nCuenta Predial Número: {}'.format(p.cuenta_predial)
producto['descripcion'] += info
producto['unidad'] = '{}\n({})'.format( producto['unidad'] = '{}\n({})'.format(
p.producto.unidad.name, p.producto.unidad.key) p.producto.unidad.name, p.producto.unidad.key)
producto['cantidad'] = str(p.cantidad) producto['cantidad'] = str(p.cantidad)
@ -4184,9 +4190,10 @@ help_lr = 'Listar RFCs'
@click.option('-t', '--test', is_flag=True, default=False) @click.option('-t', '--test', is_flag=True, default=False)
@click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False) @click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False)
@click.option('-ip', '--importar-productos', is_flag=True, default=False) @click.option('-ip', '--importar-productos', is_flag=True, default=False)
@click.option('-bk', '--backup-dbs', is_flag=True, default=False)
def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc,
borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre, test, borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre, test,
generar_archivo_productos, importar_productos): generar_archivo_productos, importar_productos, backup_dbs):
opt = locals() opt = locals()
@ -4278,6 +4285,9 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc,
_importar_productos(opt['archivo']) _importar_productos(opt['archivo'])
sys.exit(0) sys.exit(0)
if opt['backup_dbs']:
util.backup_dbs()
return return

View File

@ -14,6 +14,11 @@ try:
except ImportError: except ImportError:
DEFAULT_PASSWORD = 'blades3.3' DEFAULT_PASSWORD = 'blades3.3'
try:
from conf import SEAFILE_SERVER
except ImportError:
SEAFILE_SERVER = {}
DEBUG = DEBUG DEBUG = DEBUG
VERSION = '0.2.1' VERSION = '0.2.1'

View File

@ -148,7 +148,7 @@ var emisor_otros_datos= [
{view: 'text', id: 'token_timbrado', {view: 'text', id: 'token_timbrado',
name: 'token_timbrado', label: 'Token de Timbrado: '}, name: 'token_timbrado', label: 'Token de Timbrado: '},
{view: 'text', id: 'token_soporte', {view: 'text', id: 'token_soporte',
name: 'token_soporte', label: 'Token de Soporte: '}, name: 'token_soporte', label: 'Token para Respaldos: '},
] ]