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)