From 163e0f50c94043920ca93df761fd0512c76dc1e0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 1 Apr 2019 23:09:32 -0600 Subject: [PATCH] =?UTF-8?q?Env=C3=ADo=20de=20n=C3=B3mina=20por=20correo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++ VERSION | 2 +- source/app/controllers/main.py | 3 +- source/app/models/db.py | 9 +- source/app/models/main.py | 115 +++++++++++++++++++------ source/app/settings.py | 2 +- source/static/js/controller/nomina.js | 40 ++++++++- source/static/js/controller/tickets.js | 18 +++- source/static/js/ui/admin.js | 2 +- source/static/js/ui/invoices.js | 2 +- source/static/js/ui/nomina.js | 3 + 11 files changed, 163 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a26a4f6..12283ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v 1.31.0 [00-abr-2019] +---------------------- + - Error: Validar cantidad o valor unitario cero en tickets + - Mejora: Envío de nómina por correo al empleado + + v 1.30.0 [24-mar-2019] ---------------------- - Mejora: Se actualizan los catálogos de Nómina diff --git a/VERSION b/VERSION index 034552a..34aae15 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.30.0 +1.31.0 diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 9919aca..41ff153 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -498,7 +498,8 @@ class AppNomina(object): def on_post(self, req, resp): values = req.params - req.context['result'] = self._db.nomina(values) + session = req.env['beaker.session'] + req.context['result'] = self._db.nomina(values, session['userobj']) resp.status = falcon.HTTP_200 def on_delete(self, req, resp): diff --git a/source/app/models/db.py b/source/app/models/db.py index ca73fd0..690f434 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -33,11 +33,6 @@ class StorageEngine(object): def get_nomina(self, values): return main.CfdiNomina.get_by(values) - def nomina(self, values): - opt = values.pop('opt') - if opt == 'cancel': - return main.CfdiNomina.cancel(int(values['id'])) - def empresa_agregar(self, values): return main.empresa_agregar(values['alta_rfc'], False) @@ -461,3 +456,7 @@ class StorageEngine(object): def partners_accounts_bank(self, values): return main.SociosCuentasBanco.post(values) + + def nomina(self, values, user): + return main.CfdiNomina.post(values, user) + diff --git a/source/app/models/main.py b/source/app/models/main.py index 375cd8f..4d441c8 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -7593,6 +7593,94 @@ class CfdiNomina(BaseModel): class Meta: order_by = ('fecha',) + def _cancel(self, values, user): + id = int(values['id']) + msg = 'Recibo cancelado correctamente' + auth = Emisor.get_auth() + certificado = Certificado.select()[0] + obj = CfdiNomina.get(CfdiNomina.id==id) + + if obj.uuid is None: + msg = 'Solo se pueden cancelar recibos timbrados' + return {'ok': False, 'msg': msg} + + data, result = util.cancel_xml(auth, obj.uuid, certificado) + + if data['ok']: + data['msg'] = 'Recibo cancelado correctamente' + data['row']['estatus'] = 'Cancelado' + obj.estatus = data['row']['estatus'] + obj.error = '' + obj.cancelada = True + obj.fecha_cancelacion = result['Fecha'] + obj.acuse = result['Acuse'] or '' + else: + obj.error = data['msg'] + obj.save() + return data + + def _send_mail(self, values, user): + id = int(values['id']) + msg = 'Recibo de Nómina enviado correctamente' + result = {'ok': True, 'msg': msg} + + config = Configuracion.get_({'fields': 'correo'}) + contra = Configuracion.get_('correo_contra') + if not config: + msg = 'Envío de correo no configurado.' + result['ok'] = False + result['msg'] = msg + return result + + emisor = Emisor.select()[0] + obj = CfdiNomina.get(CfdiNomina.id==id) + to = obj.empleado.correo + if not to: + msg = 'El empleado no tiene correo.' + result['ok'] = False + result['msg'] = msg + return result + + name = '{}{}_{}'.format(obj.serie, obj.folio, obj.empleado.rfc) + values = self._get_not_in_xml(self, obj, emisor) + data = util.get_data_from_xml(obj, values) + doc = util.to_pdf(data, emisor.rfc) + files = ( + (obj.xml, f'{name}.xml'), + (doc, f'{name}.pdf'), + ) + + message = subject = f"Enviamos tu recibo de nómina" + server = { + 'server': config['correo_servidor'], + 'port': config['correo_puerto'], + 'ssl': utils.to_bool(config['correo_ssl']), + 'user': config['correo_usuario'], + 'pass': utils.decrypt(contra, emisor.rfc), + } + mail = { + 'to': to, + 'copy': config['correo_copia'], + 'subject': subject, + 'message': message, + 'files': files, + } + data= { + 'server': server, + 'mail': mail, + } + r = utils.send_mail(data) + if not r['ok']: + result['ok'] = False + result['msg'] = r['msg'] + + return result + + @classmethod + def post(cls, values, user): + opt = values.pop('opt') + return getattr(cls, '_{}'.format(opt))(cls, values, user) + def _get_serie(self): serie = Configuracion.get_('txt_config_nomina_serie') if not serie: @@ -8029,6 +8117,7 @@ class CfdiNomina(BaseModel): CfdiNomina.id, CfdiNomina.serie, CfdiNomina.folio, + CfdiNomina.uuid, CfdiNomina.fecha, CfdiNomina.estatus, CfdiNomina.fecha_pago, @@ -8509,32 +8598,6 @@ class CfdiNomina(BaseModel): data += [{'id': int(r.year), 'value': int(r.year)} for r in rows] return tuple(data) - @classmethod - def cancel(cls, id): - msg = 'Recibo cancelado correctamente' - auth = Emisor.get_auth() - certificado = Certificado.select()[0] - obj = CfdiNomina.get(CfdiNomina.id==id) - - if obj.uuid is None: - msg = 'Solo se pueden cancelar recibos timbrados' - return {'ok': False, 'msg': msg} - - data, result = util.cancel_xml(auth, obj.uuid, certificado) - - if data['ok']: - data['msg'] = 'Recibo cancelado correctamente' - data['row']['estatus'] = 'Cancelado' - obj.estatus = data['row']['estatus'] - obj.error = '' - obj.cancelada = True - obj.fecha_cancelacion = result['Fecha'] - obj.acuse = result['Acuse'] or '' - else: - obj.error = data['msg'] - obj.save() - return data - class CfdiNominaDetalle(BaseModel): cfdi = ForeignKeyField(CfdiNomina) diff --git a/source/app/settings.py b/source/app/settings.py index 6c4f322..b5b809d 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -47,7 +47,7 @@ except ImportError: DEBUG = DEBUG -VERSION = '1.30.0' +VERSION = '1.31.0' EMAIL_SUPPORT = ('soporte@empresalibre.mx',) TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION) diff --git a/source/static/js/controller/nomina.js b/source/static/js/controller/nomina.js index fc74676..50cfb5d 100644 --- a/source/static/js/controller/nomina.js +++ b/source/static/js/controller/nomina.js @@ -390,6 +390,42 @@ function timbrar_nomina(){ } +function send_mail(row){ + if(!row.uuid){ + msg_error('La nómina no esta timbrada') + return + } + var data = {'opt': 'send_mail', 'id': row.id} + + msg = '¿Estás seguro de enviar por correo este Recibo de Nómina?' + webix.confirm({ + title: 'Enviar Recibo de Nómina', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + webix.ajax().post('/nomina', data, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + values = data.json(); + if(values.ok){ + msg_ok(values.msg) + }else{ + msg_error(values.msg) + } + } + }) + } + } + }) +} + + function grid_nomina_click(id, e, node){ var row = this.getItem(id) @@ -397,8 +433,8 @@ function grid_nomina_click(id, e, node){ location = '/doc/nomxml/' + row.id }else if(id.column == 'pdf'){ window.open('/doc/nompdf/' + row.id, '_blank') - //~ }else if(id.column == 'email'){ - //~ enviar_correo(row) + }else if(id.column == 'email'){ + send_mail(row) } } diff --git a/source/static/js/controller/tickets.js b/source/static/js/controller/tickets.js index 8259218..438411f 100644 --- a/source/static/js/controller/tickets.js +++ b/source/static/js/controller/tickets.js @@ -408,6 +408,14 @@ function grid_tickets_details_before_edit_stop(state, editor){ grid.editCancel() grid.unblockEvent() return true + }else if(cantidad <= 0){ + msg = 'La cantidad no puede cero o menor' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true } var valor_unitario = parseFloat(row['valor_unitario']) var descuento = parseFloat(row['descuento']) @@ -423,6 +431,14 @@ function grid_tickets_details_before_edit_stop(state, editor){ grid.editCancel() grid.unblockEvent() return true + }else if(valor_unitario <= 0){ + msg = 'El valor unitario no puede cero o menor' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true } var cantidad = parseFloat(row['cantidad']) var descuento = parseFloat(row['descuento']) @@ -784,4 +800,4 @@ function grid_tdetails_render(data){ }) var id = $$('grid_ticket_total_up').getFirstId() $$('grid_ticket_total_up').updateItem(id, {total: total}) -} \ No newline at end of file +} diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index d419f15..da12a80 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -518,7 +518,7 @@ var emisor_correo = [ {view: 'button', id: 'cmd_guardar_correo', label: 'Guardar Configuración', autowidth: true, type: 'form'}, {maxWidth: 50}, - {view: 'button', id: 'cmd_clean_email', label: 'Limpiar Configuración', + {view: 'button', id: 'cmd_clean_email', label: 'Borrar Configuración', autowidth: true, type: 'form'}, {}]} ] diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 0aa72c1..9ff88b3 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -273,7 +273,7 @@ var grid_invoices_cols = [ {id: 'html', header: 'HTML', adjust: 'data', template: get_icon('html')}, {id: 'ods', header: 'ODS', adjust: 'data', template: get_icon('table')}, {id: 'zip', header: 'ZIP', adjust: 'data', template: get_icon('zip')}, - {id: 'email', header: '', adjust: 'data', template: get_icon('email')} + {id: 'email', header: '@', adjust: 'data', template: get_icon('email')} ] diff --git a/source/static/js/ui/nomina.js b/source/static/js/ui/nomina.js index 5a84e74..93c4a8b 100644 --- a/source/static/js/ui/nomina.js +++ b/source/static/js/ui/nomina.js @@ -48,6 +48,8 @@ var grid_cols_nomina = [ {id: "serie", header: ["Serie"], adjust: "header"}, {id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header', sort: 'int', css: 'right', footer: {text: 'Recibos', colspan: 3}}, + {id: "uuid", header: ["UUID", {content: "textFilter"}], adjust: "data", + sort:"string", hidden:true}, {id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string"}, {id: "estatus", header: ["Estatus", {content: "selectFilter"}], adjust: "data", sort:"string"}, @@ -60,6 +62,7 @@ var grid_cols_nomina = [ fillspace:true, sort:"string"}, {id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')}, {id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')}, + {id: 'email', header: '@', adjust: 'data', template: get_icon('email')} ]