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
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.utils import formatdate
import os
import requests
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
@ -724,3 +727,200 @@ class ReportTemplate(BaseDocTemplate):
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
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, \
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES
#~ 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()
from settings import SEAFILE_SERVER
def _call(args):
@ -1389,6 +1384,92 @@ def crear_db(nombre):
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):
def __init__(self, path, rfc):
@ -1603,7 +1684,7 @@ class ImportFacturaLibre(object):
'11': 0.11,
'-10': 0.10,
'0': 0.0,
'-2/3': 0.666667,
'-2/3': 0.106667,
'-0.5': 0.005,
}

View File

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

View File

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

View File

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