diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33e474c..d924594 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,16 @@
+v 1.8.0 [03-jun-2018]
+---------------------
+ - Se permiten 4 decimales en Tipo de cambio
+ - Se agrega el campo {total_cantidades} al generar el PDF
+ - Se agrega opción para generar respaldos de la BD en MV
+ - Fix: Al generar con complemento EDU
+
+
v 1.7.0 [23-may-2018]
---------------------
- Se agrega soporte para truncar impuestos locales, para las estulticias de los "ingenieros" de las dependencias de gobierno
+
v 1.6.1 [09-abr-2018]
---------------------
- Fix: Nómina con separación
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..afa2b35
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.8.0
\ No newline at end of file
diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py
index 23be3e6..a192565 100644
--- a/source/app/controllers/cfdi_xml.py
+++ b/source/app/controllers/cfdi_xml.py
@@ -124,6 +124,8 @@ class CFDI(object):
if datos['donativo']:
self._donativo = True
+ self._edu = datos['edu']
+
if datos['complementos']:
if 'ine' in datos['complementos']:
self._ine = True
diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py
index df29e35..ec81ac5 100644
--- a/source/app/controllers/main.py
+++ b/source/app/controllers/main.py
@@ -17,7 +17,11 @@ class AppEmpresas(object):
def on_post(self, req, resp):
values = req.params
- req.context['result'] = self._db.empresa_agregar(values)
+ opt = values.pop('opt', '1')
+ if opt == '1':
+ req.context['result'] = self._db.empresa_agregar(values)
+ elif opt == '2':
+ req.context['result'] = self._db.respaldar_dbs()
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py
index ceac5f2..f691d99 100644
--- a/source/app/controllers/util.py
+++ b/source/app/controllers/util.py
@@ -627,6 +627,7 @@ class LIBO(object):
self._sm = None
self._desktop = None
self._currency = 'MXN'
+ self._total_cantidades = 0
if self.is_running:
ctx = uno.getComponentContext()
service = 'com.sun.star.bridge.UnoUrlResolver'
@@ -872,7 +873,7 @@ class LIBO(object):
col5.append((float(valor_unitario),))
col6.append((float(importe),))
col7.append((float(descuento),))
-
+ self._total_cantidades += float(cantidad)
if not count:
return
@@ -934,6 +935,8 @@ class LIBO(object):
}
currency = data['moneda']
+ self._set_cell('{total_cantidades}', str(self._total_cantidades))
+
cell_title = self._set_cell('{subtotal.titulo}', 'SubTotal')
value = data['subtotal']
cell_value = self._set_cell('{subtotal}', value, value=True)
@@ -1568,7 +1571,7 @@ def _comprobante(doc, options):
data['fechaformato'] = fecha.strftime('%A, %d de %B de %Y')
if 'tipocambio' in data:
- data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
+ data['tipocambio'] = 'Tipo de Cambio: $ {:0.4f}'.format(
float(data['tipocambio']))
data['notas'] = options['notas']
@@ -3411,3 +3414,20 @@ def get_timbres(rfc, token):
def truncate(value):
return trunc(value * 100) / 100
+
+def validate_path_bk():
+ path_bk = _join(str(Path.home()), DIR_FACTURAS)
+ if not os.path.isdir(path_bk):
+ msg = 'No existe la carpeta'
+ return {'ok': False, 'msg': msg}
+
+ return {'ok': True, 'msg': path_bk}
+
+
+def respaldar_db(values, path_bk):
+ user = values[0].lower()
+ db = loads(values[1])['name']
+ path = _join(path_bk, '{}.bk'.format(user))
+ args = 'pg_dump -U postgres -Fc {} > "{}"'.format(db, path)
+ _call(args)
+ return
diff --git a/source/app/models/db.py b/source/app/models/db.py
index 00176ca..c788120 100644
--- a/source/app/models/db.py
+++ b/source/app/models/db.py
@@ -28,6 +28,9 @@ class StorageEngine(object):
def empresa_borrar(self, values):
return main.empresa_borrar(values['rfc'])
+ def respaldar_dbs(self):
+ return main.respaldar_dbs()
+
def _get_empresas(self, values):
return main.get_empresas()
@@ -107,8 +110,8 @@ class StorageEngine(object):
def enviar_prefac(self, values):
return main.PreFacturas.enviar(values['id'])
- def _get_cancelinvoice(self, values):
- return main.Facturas.cancel(values['id'])
+ # ~ def _get_cancelinvoice(self, values):
+ # ~ return main.Facturas.cancel(values['id'])
def _get_statussat(self, values):
return main.Facturas.get_status_sat(values['id'])
@@ -371,8 +374,8 @@ class StorageEngine(object):
def get_preinvoices(self, values):
return main.PreFacturas.get_(values)
- def _get_timbrar(self, values):
- return main.Facturas.timbrar(values)
+ # ~ def _get_timbrar(self, values):
+ # ~ return main.Facturas.timbrar(values)
def _get_anticipoegreso(self, values):
return main.Facturas.anticipo_egreso(int(values['id']))
diff --git a/source/app/models/main.py b/source/app/models/main.py
index ebe7117..84b46a3 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -18,7 +18,7 @@ from controllers import util
from settings import log, DEBUG, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
- DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP
+ DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV
FORMAT = '{0:.2f}'
@@ -3811,7 +3811,6 @@ class Facturas(BaseModel):
FacturasComplementos.create(**data)
return
-
def _get_serie(self, user, default_serie):
if user.sucursal is None:
return default_serie
@@ -3828,7 +3827,7 @@ class Facturas(BaseModel):
emisor = Emisor.select()[0]
values['serie'] = cls._get_serie(cls, user, values['serie'])
values['folio'] = cls._get_folio(cls, values['serie'])
- values['tipo_cambio'] = float(values['tipo_cambio'])
+ values['tipo_cambio'] = round(float(values['tipo_cambio']), 4)
values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
values['anticipo'] = util.get_bool(values['anticipo'])
values['donativo'] = util.get_bool(values['donativo'])
@@ -3850,6 +3849,9 @@ class Facturas(BaseModel):
obj.total_mn = totals['total_mn']
obj.save()
+ msg = 'G {}'.format(obj.id)
+ _save_log(user.usuario, msg, 'F')
+
msg = 'Factura guardada correctamente. Enviando a timbrar'
row = {
'id': obj.id,
@@ -3859,6 +3861,7 @@ class Facturas(BaseModel):
'fecha': obj.fecha,
'tipo_comprobante': obj.tipo_comprobante,
'estatus': obj.estatus,
+ 'paid': 'No',
'total_mn': obj.total_mn,
'cliente': obj.cliente.nombre,
}
@@ -3897,7 +3900,7 @@ class Facturas(BaseModel):
comprobante['Moneda'] = invoice.moneda
comprobante['TipoCambio'] = '1'
if comprobante['Moneda'] != 'MXN':
- comprobante['TipoCambio'] = FORMAT.format(invoice.tipo_cambio)
+ comprobante['TipoCambio'] = FORMAT_TAX.format(invoice.tipo_cambio)
comprobante['Total'] = FORMAT.format(invoice.total)
comprobante['TipoDeComprobante'] = invoice.tipo_comprobante
if invoice.metodo_pago:
@@ -4190,7 +4193,7 @@ class Facturas(BaseModel):
return
@classmethod
- def timbrar(cls, values):
+ def timbrar(cls, values, user):
id = int(values['id'])
update = util.loads(values.get('update', 'true'))
@@ -4221,6 +4224,9 @@ class Facturas(BaseModel):
if update:
cls._update_inventory(cls, obj)
cls._sync(cls, id, auth)
+
+ m = 'T {}'.format(obj.id)
+ _save_log(user.usuario, m, 'F')
else:
msg = result['error']
obj.estatus = 'Error'
@@ -4379,6 +4385,16 @@ class Facturas(BaseModel):
if args['opt'] == 'invoicepayed':
return cls._set_invoices_payed(cls, args['ids'], user)
+ if args['opt'] == 'timbrar':
+ return cls.timbrar(args, user)
+
+ if args['opt'] == 'cancel':
+ result = cls.cancel(args['id'])
+ if result['ok']:
+ m = 'C {}'.format(args['id'])
+ _save_log(user.usuario, m, 'F')
+ return result
+
return {'ok': False}
@@ -7136,6 +7152,13 @@ class CfdiNominaRelacionados(BaseModel):
return [str(r.cfdi_origen.uuid) for r in query]
+@util.run_in_thread
+def _save_log(user, action, table):
+ data = {'usuario': user, 'accion': action, 'tabla': table}
+ Registro.add(data)
+ return
+
+
def authenticate(args):
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
values = util.get_con(args['rfc'])
@@ -7638,6 +7661,28 @@ def empresa_borrar(rfc):
return True
+def respaldar_dbs():
+ if not MV:
+ msg = 'Solo MV'
+ return {'ok': False, 'msg': msg}
+
+ result = util.validate_path_bk()
+ if not result['ok']:
+ return result
+ path_bk = result['msg']
+
+ data = util.get_rfcs()
+ if not len(data):
+ msg = 'Sin bases de datos a respaldar'
+ return {'ok': False, 'msg': msg}
+
+ for row in data:
+ util.respaldar_db(row, path_bk)
+
+ msg = 'Bases de datos respaldadas correctamente'
+ return {'ok': True, 'msg': msg}
+
+
def _importar_valores(archivo='', rfc=''):
if not rfc:
rfc = input('Introduce el RFC: ').strip().upper()
diff --git a/source/app/settings.py b/source/app/settings.py
index 8e83e74..b4061dd 100644
--- a/source/app/settings.py
+++ b/source/app/settings.py
@@ -31,8 +31,9 @@ except ImportError:
DEBUG = DEBUG
-VERSION = '1.5.0'
+VERSION = '1.8.0'
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
+TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js
index 77546d4..ed650d0 100644
--- a/source/static/js/controller/invoices.js
+++ b/source/static/js/controller/invoices.js
@@ -44,12 +44,14 @@ var invoices_controllers = {
$$('lst_moneda').attachEvent('onChange', lst_moneda_change)
$$('lst_tipo_comprobante').attachEvent('onChange', lst_tipo_comprobante_change)
$$('lst_serie').attachEvent('onChange', lst_serie_change)
+ $$('txt_tipo_cambio').attachEvent('onBlur', txt_tipo_cambio_lost_focus)
$$('cmd_cfdi_notes').attachEvent('onItemClick', cmd_cfdi_notes_click)
$$('cmd_admin_invoice_notes').attachEvent('onItemClick', cmd_admin_invoice_notes_click)
$$('cmd_import_invoice').attachEvent('onItemClick', cmd_import_invoice_click)
webix.extend($$('grid_invoices'), webix.ProgressBar)
+ focus('search_client_name')
}
}
@@ -465,7 +467,8 @@ function generar_anticipo_egreso(id){
function send_timbrar(id){
- webix.ajax().get('/values/timbrar', {id: id}, function(text, data){
+ //~ webix.ajax().get('/values/timbrar', {id: id}, function(text, data){
+ webix.ajax().post('invoices', {opt: 'timbrar', id: id}, function(text, data){
var values = data.json()
if(values.ok){
cmd_update_timbres_click()
@@ -1026,7 +1029,7 @@ function grid_details_before_edit_stop(state, editor){
state.value = state.value.trim()
if(state.value.length != 21){
msg = 'El Pedimento debe ser de 21 caracteres, será '
- msg += 'rechazado por el SAT. Edita estalo hasta que ya '
+ msg += 'rechazado por el SAT. Editalo hasta que ya '
msg += 'no veas este mensaje de error.
'
msg += '
Caracteres: ' + state.value.length
msg_error(msg)
@@ -1051,6 +1054,12 @@ function grid_details_before_edit_stop(state, editor){
grid.unblockEvent()
return true
}
+
+ cantidad = cantidad.round(DECIMALES)
+ grid.blockEvent()
+ state.value = cantidad
+ grid.unblockEvent()
+
var valor_unitario = parseFloat(row['valor_unitario'])
var descuento = parseFloat(row['descuento'])
}
@@ -1065,13 +1074,16 @@ function grid_details_before_edit_stop(state, editor){
grid.unblockEvent()
return true
}
+
if(cfg_invoice['with_taxes']){
var valor_unitario = price_without_taxes(parseFloat(state.value), row.id_product)
- grid.blockEvent()
- state.value = valor_unitario
- grid.unblockEvent()
+ }else{
+ var valor_unitario = parseFloat(state.value).round(DECIMALES)
}
- var valor_unitario = parseFloat(state.value)
+ grid.blockEvent()
+ state.value = valor_unitario
+ grid.unblockEvent()
+
var cantidad = parseFloat(row['cantidad'])
var descuento = parseFloat(row['descuento'])
}
@@ -1087,6 +1099,8 @@ function grid_details_before_edit_stop(state, editor){
grid.unblockEvent()
return true
}
+
+ descuento = descuento.round(DECIMALES)
var cantidad = parseFloat(row['cantidad'])
var valor_unitario = parseFloat(row['valor_unitario'])
@@ -1099,6 +1113,11 @@ function grid_details_before_edit_stop(state, editor){
grid.unblockEvent()
return true
}
+
+ grid.blockEvent()
+ state.value = descuento
+ grid.unblockEvent()
+
}
var precio_final = valor_unitario - descuento
@@ -1316,7 +1335,8 @@ function grid_invoices_click(id, e, node){
function send_cancel(id){
- webix.ajax().get('/values/cancelinvoice', {id: id}, function(text, data){
+ //~ webix.ajax().get('/values/cancelinvoice', {id: id}, function(text, data){
+ webix.ajax().post('invoices', {opt: 'cancel', id: id}, function(text, data){
var values = data.json()
if(values.ok){
msg_ok(values.msg)
@@ -1532,6 +1552,21 @@ function lst_moneda_change(nv, ov){
$$('txt_tipo_cambio').config.readonly = false
}
$$('txt_tipo_cambio').refresh()
+ select_all('txt_tipo_cambio')
+}
+
+
+function txt_tipo_cambio_lost_focus(prev){
+ var tipo_cambio = $$('txt_tipo_cambio').getValue()
+
+ if(isNaN(tipo_cambio * 1)){
+ webix.UIManager.setFocus('txt_tipo_cambio')
+ msg = 'El Tipo de Cambio debe ser un valor númerico'
+ msg_error(msg)
+ $$('txt_tipo_cambio').setValue('1.00')
+ }else{
+ $$('txt_tipo_cambio').setValue(parseFloat(tipo_cambio).round(4))
+ }
}
diff --git a/source/static/js/controller/tickets.js b/source/static/js/controller/tickets.js
index e408c8d..8259218 100644
--- a/source/static/js/controller/tickets.js
+++ b/source/static/js/controller/tickets.js
@@ -531,7 +531,8 @@ function chk_is_invoice_day_change(new_value, old_value){
function send_timbrar_invoice(id){
- webix.ajax().get('/values/timbrar', {id: id, update: false}, function(text, data){
+ //~ webix.ajax().get('/values/timbrar', {id: id, update: false}, function(text, data){
+ webix.ajax().post('invoices', {opt: 'timbrar', id: id, update: false}, function(text, data){
var values = data.json()
if(values.ok){
msg_ok(values.msg)
diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js
index 38285ff..aa7bd4d 100644
--- a/source/static/js/controller/util.js
+++ b/source/static/js/controller/util.js
@@ -53,6 +53,12 @@ function focus(name){
}
+function select_all(name){
+ focus(name)
+ $$(name).getInputNode().select()
+}
+
+
function to_end(name){
focus(name)
var txt = $$(name)
diff --git a/source/static/js/ui/empresas.js b/source/static/js/ui/empresas.js
index 41fe907..ffca9ad 100644
--- a/source/static/js/ui/empresas.js
+++ b/source/static/js/ui/empresas.js
@@ -16,6 +16,11 @@ var header = [
{view: 'button', type: 'icon', width: 40, css: 'app_button',
icon: 'home', click: 'window.location = "/"'},
]
+var footer = [
+ {},
+ {view: 'button', value: 'Respaldar BD', click: 'cmd_respaldar_bd'},
+ {},
+]
var grid_empresas_cols = [
@@ -57,6 +62,7 @@ var ui_empresas = {
}
},
grid_empresas,
+ {view: 'toolbar', elements: footer},
]}, {}, ]
},
]
diff --git a/source/static/js/ui/school.js b/source/static/js/ui/school.js
index 9746d85..0d2f892 100644
--- a/source/static/js/ui/school.js
+++ b/source/static/js/ui/school.js
@@ -22,7 +22,7 @@ var grid_cols_students = [
sort: 'string'},
{id: 'rfc', header: ['RFC', {content: 'textFilter'}], adjust: 'data',
sort: 'string'},
- {id: 'curp', header: ['CURP'], sort: 'string'},
+ {id: 'curp', header: ['CURP'], sort: 'string', adjust: 'data'},
]
diff --git a/source/templates/empresas.html b/source/templates/empresas.html
index baa03f2..3d4795f 100644
--- a/source/templates/empresas.html
+++ b/source/templates/empresas.html
@@ -42,6 +42,8 @@ function validate_nuevo_rfc(){
return
}
+ values['opt'] = 1
+
msg = '¿Estás seguro de agregar este nuevo emisor?'
webix.confirm({
title: 'Agregar Emisor',
@@ -99,6 +101,37 @@ function grid_empresas_click(id, e, node){
}
+function respaldar_bd(){
+ webix.ajax().post("/empresas", {'opt': 2}, function(text, data, xhr) {
+ var values = data.json();
+ if (values.ok) {
+ msg_ok(values.msg)
+ } else {
+ msg_error(values.msg)
+ }
+ })
+}
+
+
+function cmd_respaldar_bd(){
+ msg = '¿Estás seguro de respaldar las Bases de Datos?'
+ webix.confirm({
+ title: 'Respaldar BD',
+ ok: 'Si',
+ cancel: 'No',
+ type: 'confirm-error',
+ text: msg,
+ callback:function(result){
+ if(result){
+ msg = 'Respaldando Bases de datos...'
+ msg_ok(msg)
+ respaldar_bd()
+ }
+ }
+ })
+}
+
+
webix.ready(function(){
webix.ui(ui_empresas)
$$('grid_empresas').attachEvent('onItemClick', grid_empresas_click)