From b4f7749c6b0ca398958558a8935d1ff448d02a19 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 29 Jan 2018 00:05:54 -0600 Subject: [PATCH 1/4] Niveles educativos --- source/app/controllers/main.py | 2 + source/app/models/db.py | 11 ++++ source/app/models/main.py | 26 ++++++++ source/static/js/controller/admin.js | 92 +++++++++++++++++++++++++++- source/static/js/ui/admin.js | 68 +++++++++++++++++++- source/xslt/cadena.xslt | 2 +- source/xslt/iedu.xslt | 26 ++++++++ 7 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 source/xslt/iedu.xslt diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index a342572..4192e3b 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -150,6 +150,8 @@ class AppValues(object): req.context['result'] = self._db.importar_bdfl() elif table == 'invoicenotes': req.context['result'] = self._db.save_invoice_notes(values) + elif table == 'nivedu': + req.context['result'] = self._db.add_nivel_educativo(values) else: req.context['result'] = self._db.validate_cert(values, session) else: diff --git a/source/app/models/db.py b/source/app/models/db.py index ad8f47c..98fce0d 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -25,6 +25,12 @@ class StorageEngine(object): return getattr(self, '_get_{}'.format(table))(values, session) return getattr(self, '_get_{}'.format(table))(values) + def _get_nivedusat(self, values): + return main.SATNivelesEducativos.get_by() + + def _get_niveduall(self, values): + return main.NivelesEducativos.get_all() + def _get_titlelogin(self, values): return main.get_title_app(2) @@ -244,6 +250,8 @@ class StorageEngine(object): return main.Usuarios.remove(id) if table == 'config': return main.Configuracion.remove(id) + if table == 'nivedu': + return main.NivelesEducativos.remove(id) return False def _get_client(self, values): @@ -338,6 +346,9 @@ class StorageEngine(object): def add_folios(self, values): return main.Folios.add(values) + def add_nivel_educativo(self, values): + return main.NivelesEducativos.add(values) + def get_doc(self, type_doc, id, rfc): return main.get_doc(type_doc, id, rfc) diff --git a/source/app/models/main.py b/source/app/models/main.py index 74d6bbd..136ffb3 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1401,6 +1401,12 @@ class SATNivelesEducativos(BaseModel): def __str__(self): return self.name + @classmethod + def get_by(cls): + rows = SATNivelesEducativos.select( + SATNivelesEducativos.name).tuples() + return tuple([r[0] for r in rows]) + class NivelesEducativos(BaseModel): nombre = TextField() @@ -1415,6 +1421,26 @@ class NivelesEducativos(BaseModel): def __str__(self): return '{} ({})'.format(self.nombre, self.autorizacion) + @classmethod + def get_all(cls): + rows = NivelesEducativos.select().dicts() + return tuple(rows) + + @classmethod + def add(cls, values): + try: + NivelesEducativos.create(**values) + result = {'ok': True} + except IntegrityError: + msg = 'Nivel Educativo existente' + result = {'ok': False, 'msg': msg} + return result + + @classmethod + def remove(cls, id): + q = NivelesEducativos.delete().where(NivelesEducativos.id==int(id)) + return bool(q.execute()) + class Grupos(BaseModel): nivel = ForeignKeyField(NivelesEducativos) diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index b7a4ea5..5e8fa9c 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -23,6 +23,7 @@ var controllers = { $$('emisor_logo').attachEvent('onItemClick', emisor_logo_click) $$('cmd_emisor_agregar_cuenta').attachEvent('onItemClick', cmd_emisor_agregar_cuenta_click) $$('cmd_emisor_eliminar_cuenta').attachEvent('onItemClick', cmd_emisor_eliminar_cuenta_click) + $$('cmd_niveles_educativos').attachEvent('onItemClick', cmd_niveles_educativos_click) $$('emisor_cuenta_saldo_inicial').attachEvent('onChange', emisor_cuenta_saldo_inicial_change) //~ SAT tb_sat = $$('tab_sat').getTabbar() @@ -479,9 +480,9 @@ function emisor_postal_code_key_press(code, e){ function chk_escuela_change(new_value, old_value){ var value = Boolean(new_value) if (value){ - $$('cmd_niveles').enable() + $$('cmd_niveles_educativos').enable() } else { - $$('cmd_niveles').disable() + $$('cmd_niveles_educativos').disable() } } @@ -1836,3 +1837,90 @@ function txt_ticket_printer_key_press(code, e){ }) } + + +function cmd_niveles_educativos_click(){ + admin_ui_niveles_educativos.init() + $$('win_niveles_educativos').show() + get_niveles_educativos() +} + + +function get_niveles_educativos(){ + webix.ajax().sync().get('/values/niveduall', { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + $$('grid_niveles_educativos').clearAll() + $$('grid_niveles_educativos').parse(values) + $$('grid_niveles_educativos').refresh() + } + }) +} + + +function add_nivel_educativo_click(){ + var form = $$('form_niveles_educativos') + + if (!form.validate()){ + msg = 'Valores inválidos' + msg_error(msg) + return + } + + var values = form.getValues() + + webix.ajax().post('/values/nivedu', values, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var result = data.json() + form.setValues({}) + if(result.ok){ + $$('grid_niveles_educativos').add(values) + }else{ + msg_error(result.msg) + } + } + }) + +} + + +function delete_nivel_educativo(id){ + webix.ajax().del('/values/nivedu', {id: id}, function(text, xml, xhr){ + if(xhr.status == 200){ + $$('grid_niveles_educativos').remove(id) + }else{ + msg = 'No se pudo eliminar' + msg_error(msg) + } + }) +} + + +function grid_niveles_educativos_click(id){ + if(id.column != 'delete'){ + return + } + + msg = '¿Estás seguro de eliminar este Nivel Educativo' + webix.confirm({ + title: 'Eliminar', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + delete_nivel_educativo(id.row) + } + } + }) + +} \ No newline at end of file diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index e5d454f..76b5f75 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -48,6 +48,72 @@ var admin_ui_windows = { } +var grid_cols_niveles_educativos = [ + {id: 'id', header: 'ID', hidden: true}, + {id: 'delete', header: '', width: 30, css: 'delete'}, + {id: 'nombre', header: 'Nivel', fillspace: 1}, + {id: 'autorizacion', header: 'Autorización', fillspace: 1}, +] + + +var grid_niveles_educativos = { + view: 'datatable', + id: 'grid_niveles_educativos', + select: 'cell', + adjust: true, + autoheight: true, + headermenu: true, + columns: grid_cols_niveles_educativos, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.delete = '-' + }) + } + }, +} + + +var form_controls_niveles_educativos = [ + {cols: [ + {view: 'text', id: 'txt_nivel_educativo', label: 'Nivel', suggest: '/values/nivedusat', + labelPosition: 'top', name: 'nombre', required: true}, + {view: "text", id: 'txt_auth_rvoe', label: 'Autorización', + name: 'autorizacion', labelPosition: 'top'}, + {view: 'button', type: 'iconTop', icon: 'plus', label: 'Agregar', + autowidth: true, click: function(){ + add_nivel_educativo_click() + }} + ]}, + grid_niveles_educativos, + {}, + {cols:[{}, + {view: "button", value: 'Cerrar', click: "$$('win_niveles_educativos').close()"}, + {}]} +] + + +var admin_ui_niveles_educativos = { + init: function(){ + webix.ui({ + view: 'window', + id: 'win_niveles_educativos', + head: 'Niveles Educativos', + width: 500, + modal: true, + position: 'center', + body: { + view: 'form', id: 'form_niveles_educativos', + elements: form_controls_niveles_educativos + } + }) + + $$('grid_niveles_educativos').attachEvent('onItemClick', grid_niveles_educativos_click) + + }, +} + + var menu_data = [ {id: 'app_home', icon: 'dashboard', value: 'Inicio'}, {id: 'app_emisor', icon: 'user-circle', value: 'Emisor'}, @@ -129,7 +195,7 @@ var emisor_otros_datos= [ {template: 'Escuela', type: 'section'}, {cols: [{view: 'checkbox', id: 'chk_escuela', name: 'es_escuela', label: 'Es Escuela'}, - {view: 'button', id: 'cmd_niveles', label: 'Niveles Escolares', + {view: 'button', id: 'cmd_niveles_educativos', label: 'Niveles Educativos', type: 'form', align: 'center', autowidth: true, disabled: true}, {}, {}]}, {template: 'ONG', type: 'section'}, diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt index be01a88..8ed4715 100644 --- a/source/xslt/cadena.xslt +++ b/source/xslt/cadena.xslt @@ -11,6 +11,7 @@ + + + + + + + + + + + + + + + + + + + + + + + From c21d14fe35d5cc95c06b76993349695c426c003b Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 29 Jan 2018 01:55:42 -0600 Subject: [PATCH 2/4] Agregar alumnos --- source/app/controllers/main.py | 23 +++++ source/app/main.py | 3 +- source/app/models/db.py | 8 ++ source/app/models/main.py | 42 +++++++- source/static/js/controller/main.js | 21 +++- source/static/js/controller/school.js | 70 +++++++++++++ source/static/js/controller/util.js | 6 ++ source/static/js/ui/main.js | 1 + source/static/js/ui/school.js | 143 ++++++++++++++++++++++++++ source/templates/main.html | 2 + 10 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 source/static/js/controller/school.js create mode 100644 source/static/js/ui/school.js diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 4192e3b..59cb9e1 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -221,6 +221,29 @@ class AppPartners(object): resp.status = falcon.HTTP_204 +class AppStudents(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + req.context['result'] = self._db.get_students(values) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.students(values) + resp.status = falcon.HTTP_200 + + def on_delete(self, req, resp): + values = req.params + if self._db.delete('students', values['id']): + resp.status = falcon.HTTP_200 + else: + resp.status = falcon.HTTP_204 + + class AppProducts(object): def __init__(self, db): diff --git a/source/app/main.py b/source/app/main.py index 2aa6e39..21c96ee 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -16,7 +16,7 @@ from controllers.main import (AppEmpresas, AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig, AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios, AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco, - AppMovimientosBanco, AppTickets + AppMovimientosBanco, AppTickets, AppStudents ) @@ -52,6 +52,7 @@ api.add_route('/preinvoices', AppPreInvoices(db)) api.add_route('/tickets', AppTickets(db)) api.add_route('/cuentasbanco', AppCuentasBanco(db)) api.add_route('/movbanco', AppMovimientosBanco(db)) +api.add_route('/students', AppStudents(db)) # ~ Activa si usas waitress y NO estas usando servidor web diff --git a/source/app/models/db.py b/source/app/models/db.py index 98fce0d..0e6b1f0 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -299,6 +299,14 @@ class StorageEngine(object): #~ return main.PreFacturas.actualizar(values, id) return main.PreFacturas.add(values) + def get_students(self, values): + return main.Alumnos.get_by(values) + + def students(self, values): + opt = values.pop('opt') + if opt == 'add': + return main.Alumnos.add(values['values']) + def tickets(self, values, user): opt = values.pop('opt') if opt == 'add': diff --git a/source/app/models/main.py b/source/app/models/main.py index 136ffb3..f5b4cb5 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -195,12 +195,13 @@ def config_main(): punto_de_venta = util.get_bool(Configuracion.get_('chk_usar_punto_de_venta')) data = { 'empresa': get_title_app(3), - 'punto_de_venta': punto_de_venta - + 'punto_de_venta': punto_de_venta, + 'escuela': False, } if not obj is None: titulo = '{} - {}' data['empresa'] = titulo.format(data['empresa'], obj.nombre) + data['escuela'] = obj.es_escuela return data @@ -2154,6 +2155,43 @@ class Alumnos(BaseModel): class Meta: order_by = ('nombre', 'paterno') + def _clean(self, values): + fields = util.clean(util.loads(values)) + fields['rfc'] = fields['rfc'].upper() + fields['curp'] = fields['curp'].upper() + fields['nombre'] = util.spaces(fields['nombre']) + fields['paterno'] = util.spaces(fields['paterno']) + fields['materno'] = util.spaces(fields['materno']) + return fields + + def _get(self, where): + rows = (Alumnos + .select() + .where(where) + .dicts() + ) + return tuple(rows) + + @classmethod + def get_by(cls, values): + if not values: + w = None + + return cls._get(cls, w) + + @classmethod + def add(cls, values): + fields = cls._clean(cls, values) + try: + obj = Alumnos.create(**fields) + except IntegrityError as e: + msg = 'Ya existe un alumno con este CURP' + data = {'ok': False, 'msg': msg} + return data + + data = {'ok': True} + return data + class AlumnosParientes(BaseModel): alumno = ForeignKeyField(Alumnos) diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index d2bbf9e..5e4575d 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -10,12 +10,21 @@ function configuracion_inicial(){ var values = data.json() $$('lbl_title_main').setValue(values.empresa) //~ showvar() + var pos = 4 + if(values.escuela){ + var node = { + id: 'app_school', + icon: 'graduation-cap', + value: 'Escuela'} + $$('main_sidebar').add(node, pos) + pos += 1 + } if(values.punto_de_venta){ var node = { id: 'app_tickets', icon: 'money', value: 'Punto de venta'} - $$('main_sidebar').add(node, 4) + $$('main_sidebar').add(node, pos) } }) @@ -45,6 +54,7 @@ var controllers = { products_controllers.init() bancos_controllers.init() invoices_controllers.init() + controllers_school.init() tickets_controllers.init() } } @@ -113,6 +123,7 @@ function current_dates(){ function multi_change(prevID, nextID){ + if(nextID == 'app_partners'){ active = $$('multi_partners').getActiveId() if(active == 'partners_home'){ @@ -137,6 +148,14 @@ function multi_change(prevID, nextID){ return } + if(nextID == 'app_school'){ + active = $$('multi_school').getActiveId() + if(active == 'school_home'){ + init_config_school() + } + return + } + if(nextID == 'app_tickets'){ active = $$('multi_tickets').getActiveId() if(active == 'tickets_home'){ diff --git a/source/static/js/controller/school.js b/source/static/js/controller/school.js new file mode 100644 index 0000000..5f5a080 --- /dev/null +++ b/source/static/js/controller/school.js @@ -0,0 +1,70 @@ + + +var controllers_school = { + init: function(){ + $$('cmd_new_student').attachEvent('onItemClick', cmd_new_student_click) + $$('cmd_save_student').attachEvent('onItemClick', cmd_save_student_click) + $$('cmd_cancel_student').attachEvent('onItemClick', cmd_cancel_student_click) + } +} + + +function init_config_school(){ + get_students() +} + + +function cmd_new_student_click(){ + $$('grid_students').clearSelection() + $$('multi_school').setValue('new_student') +} + + +function cmd_cancel_student_click(){ + $$('multi_school').setValue('school_home') +} + + +function cmd_save_student_click(){ + var msg = 'Valores inválidos' + var form = this.getFormView(); + + if (!form.validate()) { + msg_error(msg) + return + } + + var values = form.getValues(); + + webix.ajax().post('/students', {opt: 'add', values: values}, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico'; + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json(); + if (values.ok) { + form.setValues({}) + $$('multi_school').setValue('school_home') + get_students() + } else { + msg_error(values.msg) + } + } + }) + +} + + +function get_students(){ + webix.ajax().get('/students', {}, { + error: function(text, data, xhr) { + msg_error('Error al consultar') + }, + success: function(text, data, xhr) { + var values = data.json() + $$('grid_students').clearAll() + $$('grid_students').parse(values) + } + }) +} \ No newline at end of file diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index 0081054..de495ba 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -377,6 +377,12 @@ function validate_pedimento(value){ } +function validate_curp(value){ + var pattern = '[A-Z][A,E,I,O,U,X][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][M,H][A-Z]{2}[B,C,D,F,G,H,J,K,L,M,N,Ñ,P,Q,R,S,T,V,W,X,Y,Z]{3}[0-9,A-Z][0-9]' + return validate_regexp(value, pattern) +} + + //config may as well include only text, color and date hash webix.editors.$popup = { text:{ diff --git a/source/static/js/ui/main.js b/source/static/js/ui/main.js index cd6bdaf..7462eae 100644 --- a/source/static/js/ui/main.js +++ b/source/static/js/ui/main.js @@ -37,6 +37,7 @@ var multi_main = { app_partners, app_products, app_bancos, + app_school, app_tickets, app_invoices, ], diff --git a/source/static/js/ui/school.js b/source/static/js/ui/school.js new file mode 100644 index 0000000..2af1a23 --- /dev/null +++ b/source/static/js/ui/school.js @@ -0,0 +1,143 @@ + + + + +var toolbar_students = [ + {view: 'button', id: 'cmd_new_student', label: 'Nuevo', type: 'iconButton', + autowidth: true, icon: 'user-plus'}, + {view: 'button', id: 'cmd_edit_student', label: 'Editar', type: 'iconButton', + autowidth: true, icon: 'user'}, + {view: 'button', id: 'cmd_delete_student', label: 'Eliminar', type: 'iconButton', + autowidth: true, icon: 'user-times'}, +] + + +var grid_cols_students = [ + {id: 'index', header:'#', css: 'right', + footer: {content: 'countRows', colspan: 2, css: 'right'}}, + {id: 'id', header: 'Clave', sort: 'int', css: 'right'}, + {id: 'nombre', header: ['Nombre', {content: 'textFilter'}], + sort: 'string'}, + {id: 'paterno', header: ['A. Paterno', {content: 'textFilter'}], + sort: 'string'}, + {id: 'materno', header: ['A. Materno', {content: 'textFilter'}], + sort: 'string'}, + {id: 'rfc', header: ['RFC', {content: 'textFilter'}], adjust: 'data', + sort: 'string'}, + {id: 'curp', header: ['CURP'], sort: 'string'}, +] + + +var grid_students = { + view: 'datatable', + id: 'grid_students', + select: 'row', + adjust: true, + footer: true, + resizeColumn: true, + headermenu: true, + columns: grid_cols_students, + ready:function(){ + this.adjustColumn('index'); + this.adjustColumn('id'); + this.adjustColumn('nombre'); + this.adjustColumn('rfc'); + this.adjustColumn('curp'); + }, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i+1; + }) + } + }, +} + + +var rows_school_home = [ + {view: 'toolbar', elements: toolbar_students}, + grid_students, +] + + +var student_controls_generales = [ + {view: 'text', id: 'student_name', name: 'nombre', label: 'Nombre: ', + required: true, invalidMessage: 'El nombre es requerido'}, + {view: 'text', id: 'student_paterno', name: 'paterno', label: 'Apellido Paterno: ', + required: true, invalidMessage: 'El apellido paterno es requerido'}, + {view: 'text', id: 'student_materno', name: 'materno', + label: 'Apellido Materno: '}, + {cols: [ + {view: 'text', id: 'student_rfc', name: 'rfc', label: 'RFC: ', + required: true, invalidMessage: 'RFC inválido', adjust: 'data', + attributes: {maxlength: 13}}, + {view: 'text', id: 'student_curp', name: 'curp', label: 'CURP: ', + required: true, invalidMessage: 'CURP inválido', adjust: 'data', + attributes: {maxlength: 20}}, + {}]}, +] + + +var form_controls_student = [ + { + view: 'tabview', + id: 'tab_student', + tabbar: {options: ['Datos Generales']}, animate: true, + cells: [ + {id: 'Datos Generales', rows: student_controls_generales}, + ] + }, + {rows: [ + { template:"", type: "section" }, + { margin: 10, cols: [{}, + {view: "button", id: "cmd_save_student", label: "Guardar" , + type: "form", autowidth: true, align: "center"}, + {view: "button", id: "cmd_cancel_student", label: "Cancelar" , + type: "danger", autowidth: true, align: "center"}, + {}] + }, + ]} +] + + +var form_student = { + type: 'space', + cols: [{ + view: 'form', + id: 'form_student', + complexData: true, + scroll: true, + elements: form_controls_student, + elementsConfig: { + labelWidth: 150, + labelAlign: 'right' + }, + rules: { + nombre: function(value){ return value.trim() != '';}, + rfc: validate_rfc, + curp: validate_curp, + } + }] +} + + +var multi_school = { + id: 'multi_school', + view: 'multiview', + animate: true, + cells:[ + {id: 'school_home', rows: rows_school_home}, + {id: 'school_groups', rows: []}, + {id: 'new_student', rows: [form_student]}, + ], +} + + +var app_school = { + id: 'app_school', + rows:[ + {view: 'template', id: 'th_school', type: 'header', + template: 'Administración de Escuela'}, + multi_school + ], +} diff --git a/source/templates/main.html b/source/templates/main.html index d5395ca..eb2a7aa 100644 --- a/source/templates/main.html +++ b/source/templates/main.html @@ -8,6 +8,7 @@ + @@ -15,6 +16,7 @@ + From 85ff55a2db5feb9d0e45b4a4bcb188110fd86a57 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 29 Jan 2018 10:43:25 -0600 Subject: [PATCH 3/4] Editar alumnos --- source/app/models/db.py | 7 +++ source/app/models/main.py | 45 +++++++++++++- source/static/js/controller/school.js | 88 ++++++++++++++++++++++++++- source/static/js/ui/school.js | 8 ++- 4 files changed, 143 insertions(+), 5 deletions(-) diff --git a/source/app/models/db.py b/source/app/models/db.py index 0e6b1f0..62bc004 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -25,6 +25,9 @@ class StorageEngine(object): return getattr(self, '_get_{}'.format(table))(values, session) return getattr(self, '_get_{}'.format(table))(values) + def _get_schoolgroups(self, values): + return main.Grupos.get_by(values) + def _get_nivedusat(self, values): return main.SATNivelesEducativos.get_by() @@ -252,6 +255,8 @@ class StorageEngine(object): return main.Configuracion.remove(id) if table == 'nivedu': return main.NivelesEducativos.remove(id) + if table == 'students': + return main.Alumnos.remove(id) return False def _get_client(self, values): @@ -306,6 +311,8 @@ class StorageEngine(object): opt = values.pop('opt') if opt == 'add': return main.Alumnos.add(values['values']) + if opt == 'edit': + return main.Alumnos.actualizar(values['values']) def tickets(self, values, user): opt = values.pop('opt') diff --git a/source/app/models/main.py b/source/app/models/main.py index f5b4cb5..ff87883 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1427,10 +1427,16 @@ class NivelesEducativos(BaseModel): rows = NivelesEducativos.select().dicts() return tuple(rows) + def _add_group(self, obj): + Grupos.get_or_create(**{'nivel': obj}) + return + @classmethod def add(cls, values): try: - NivelesEducativos.create(**values) + obj = NivelesEducativos.create(**values) + # Revisar + cls._add_group(cls, obj) result = {'ok': True} except IntegrityError: msg = 'Nivel Educativo existente' @@ -1457,6 +1463,17 @@ class Grupos(BaseModel): def __str__(self): return '{} {} {}'.format(self.nivel.nombre, self.grado, self.nombre) + @classmethod + def get_by(cls, values): + rows = (Grupos.select( + Grupos.id.alias('id'), + NivelesEducativos.nombre.alias('value')) + .join(NivelesEducativos) + .switch(Grupos) + .dicts() + ) + return tuple(rows) + class CuentasBanco(BaseModel): de_emisor = BooleanField(default=False) @@ -2174,6 +2191,12 @@ class Alumnos(BaseModel): @classmethod def get_by(cls, values): + if 'id' in values: + id = int(values['id']) + w = (Alumnos.id==id) + rows = cls._get(cls, w) + return rows[0] + if not values: w = None @@ -2192,6 +2215,26 @@ class Alumnos(BaseModel): data = {'ok': True} return data + @classmethod + def actualizar(cls, values): + fields = cls._clean(cls, values) + id = int(fields.pop('id')) + try: + q = Alumnos.update(**fields).where(Alumnos.id==id) + q.execute() + except IntegrityError: + msg = 'Ya existe un Alumno con este CURP' + data = {'ok': False, 'msg': msg} + return data + + data = {'ok': True} + return data + + @classmethod + def remove(cls, id): + q = Alumnos.delete().where(Alumnos.id==id) + return bool(q.execute()) + class AlumnosParientes(BaseModel): alumno = ForeignKeyField(Alumnos) diff --git a/source/static/js/controller/school.js b/source/static/js/controller/school.js index 5f5a080..5e99840 100644 --- a/source/static/js/controller/school.js +++ b/source/static/js/controller/school.js @@ -3,30 +3,110 @@ var controllers_school = { init: function(){ $$('cmd_new_student').attachEvent('onItemClick', cmd_new_student_click) + $$('cmd_edit_student').attachEvent('onItemClick', cmd_edit_student_click) + $$('cmd_delete_student').attachEvent('onItemClick', cmd_delete_student_click) $$('cmd_save_student').attachEvent('onItemClick', cmd_save_student_click) $$('cmd_cancel_student').attachEvent('onItemClick', cmd_cancel_student_click) } } +function get_school_groups(){ + webix.ajax().get('/values/schoolgroups', { + error: function(text, data, xhr) { + }, + success: function(text, data, xhr) { + var values = data.json(); + $$('student_grupo').define('suggest', values) + $$('student_grupo').refresh() + } + }) +} + + function init_config_school(){ get_students() + get_school_groups() } function cmd_new_student_click(){ + $$('form_student').setValues({}) $$('grid_students').clearSelection() $$('multi_school').setValue('new_student') } +function cmd_edit_student_click(){ + var row = $$('grid_students').getSelectedItem() + + if (row == undefined){ + msg = 'Selecciona un Alumno' + msg_error(msg) + return + } + + webix.ajax().get('/students', {id: row['id']}, { + error: function(text, data, xhr) { + msg_error() + }, + success: function(text, data, xhr){ + var values = data.json() + $$('form_student').setValues(values) + } + }) + + $$('multi_school').setValue('new_student') +} + + +function delete_student(id){ + webix.ajax().del('/students', {id: id}, function(text, xml, xhr){ + msg = 'Alumno eliminado correctamente' + if (xhr.status == 200){ + $$('grid_students').remove(id); + msg_ok(msg) + } else { + msg = 'No se pudo eliminar.' + msg_error(msg) + } + }) +} + + +function cmd_delete_student_click(){ + var row = $$('grid_students').getSelectedItem() + + if (row == undefined){ + msg = 'Selecciona un Alumno' + msg_error(msg) + return + } + + msg = '¿Estás seguro de eliminar al Alumno?

' + msg += row['nombre'] + ' ' + row['paterno'] + ' (' + row['rfc'] + ')' + msg += '

ESTA ACCIÓN NO SE PUEDE DESHACER

' + webix.confirm({ + title:'Eliminar Alumno', + ok:'Si', + cancel:'No', + type:'confirm-error', + text:msg, + callback:function(result){ + if (result){ + delete_student(row['id']) + } + } + }) +} + + function cmd_cancel_student_click(){ $$('multi_school').setValue('school_home') } function cmd_save_student_click(){ - var msg = 'Valores inválidos' var form = this.getFormView(); if (!form.validate()) { @@ -35,8 +115,12 @@ function cmd_save_student_click(){ } var values = form.getValues(); + opt = 'add' + if(values.id){ + opt = 'edit' + } - webix.ajax().post('/students', {opt: 'add', values: values}, { + webix.ajax().post('/students', {opt: opt, values: values}, { error:function(text, data, XmlHttpRequest){ msg = 'Ocurrio un error, consulta a soporte técnico'; msg_error(msg) diff --git a/source/static/js/ui/school.js b/source/static/js/ui/school.js index 2af1a23..002dfc4 100644 --- a/source/static/js/ui/school.js +++ b/source/static/js/ui/school.js @@ -1,7 +1,5 @@ - - var toolbar_students = [ {view: 'button', id: 'cmd_new_student', label: 'Nuevo', type: 'iconButton', autowidth: true, icon: 'user-plus'}, @@ -75,6 +73,12 @@ var student_controls_generales = [ required: true, invalidMessage: 'CURP inválido', adjust: 'data', attributes: {maxlength: 20}}, {}]}, + {cols: [ + {view: 'richselect', id: 'student_grupo', name: 'grupo', + label: 'Nivel Educativo: ', required: true, options: [], + invalidMessage: 'El Nivel Educativo es requerido'}, + {}, + ]}, ] From 7a573b8eda9703a01dcd98a2c812a2c8729c1ece Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 29 Jan 2018 13:21:06 -0600 Subject: [PATCH 4/4] Complemento EDU --- source/app/controllers/cfdi_xml.py | 34 +++++++++++-------- source/app/controllers/util.py | 7 ++++ source/app/models/db.py | 3 ++ source/app/models/main.py | 45 ++++++++++++++++++++++++- source/static/js/controller/invoices.js | 25 ++++++++++++++ source/static/js/ui/invoices.js | 38 ++++++++++++++++++++- 6 files changed, 136 insertions(+), 16 deletions(-) diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index 8b933d9..445ef34 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -58,6 +58,12 @@ SAT = { 'xmlns': 'http://www.sat.gob.mx/ine', 'schema': ' http://www.sat.gob.mx/ine http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd', }, + 'edu': { + 'version': '1.0', + 'prefix': 'iedu', + 'xmlns': 'http://www.sat.gob.mx/iedu', + 'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/ine/iedu.xsd', + }, } @@ -72,6 +78,7 @@ class CFDI(object): self._impuestos_locales = False self._donativo = False self._ine = False + self._edu = False self.error = '' def _now(self): @@ -118,6 +125,8 @@ class CFDI(object): if 'ine' in datos['complementos']: self._ine = True + self._edu = datos['edu'] + if 'nomina' in datos: return self._validate_nomina(datos) return True @@ -160,10 +169,16 @@ class CFDI(object): if self._ine: name = 'xmlns:{}'.format(SAT['ine']['prefix']) attributes[name] = SAT['ine']['xmlns'] - schema_donativo = SAT['ine']['schema'] + schema_ine = SAT['ine']['schema'] + + schema_edu = '' + if self._edu: + name = 'xmlns:{}'.format(SAT['edu']['prefix']) + attributes[name] = SAT['edu']['xmlns'] + schema_edu = SAT['edu']['schema'] attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \ - schema_locales + schema_donativo +schema_ine + schema_locales + schema_donativo + schema_ine + schema_edu attributes.update(datos) if not 'Version' in attributes: @@ -213,6 +228,7 @@ class CFDI(object): complemento = row.pop('complemento') cuenta_predial = row.pop('CuentaPredial', '') pedimento = row.pop('Pedimento', '') + student = row.pop('student', '') taxes = {} if 'impuestos' in row: @@ -246,20 +262,10 @@ class CFDI(object): node_name = '{}:CuentaPredial'.format(self._pre) ET.SubElement(concepto, node_name, attributes) - if 'autRVOE' in row: - fields = ( - 'version', - 'nombreAlumno', - 'CURP', - 'nivelEducativo', - 'autRVOE', - ) - for field in fields: - if field in row['autRVOE']: - attributes[field] = row['autRVOE'][field] + if student: node_name = '{}:ComplementoConcepto'.format(self._pre) complemento = ET.SubElement(concepto, node_name) - ET.SubElement(complemento, 'iedu:instEducativas', attributes) + ET.SubElement(complemento, 'iedu:instEducativas', student) return def _impuestos(self, datos): diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 38bb216..1364728 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -1145,6 +1145,13 @@ def _conceptos(doc, version): info = '\nNúmero Pedimento: {}'.format(v['numeropedimento']) values['descripcion'] += info + n = c.find('{}ComplementoConcepto'.format(PRE[version])) + if n is not None: + v = CaseInsensitiveDict(n[0].attrib.copy()) + info = '\nAlumno: {} (CURP: {})\nNivel: {}, Autorización: {}'.format( + v['nombreAlumno'], v['CURP'], v['nivelEducativo'], v['autRVOE']) + values['descripcion'] += info + data.append(values) return data diff --git a/source/app/models/db.py b/source/app/models/db.py index 62bc004..84a9b78 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -262,6 +262,9 @@ class StorageEngine(object): def _get_client(self, values): return main.Socios.get_by_client(values) + def _get_student(self, values): + return main.Alumnos.get_by_name(values) + def _get_product(self, values): return main.Productos.get_by(values) diff --git a/source/app/models/main.py b/source/app/models/main.py index ff87883..5d234c0 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -216,6 +216,7 @@ def config_timbrar(): 'cfdi_donativo': obj.es_ong, 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), 'cfdi_ine': Configuracion.get_bool('chk_config_ine'), + 'cfdi_edu': Configuracion.get_bool('chk_config_edu'), 'cfdi_metodo_pago': Configuracion.get_bool('chk_config_ocultar_metodo_pago'), 'cfdi_condicion_pago': Configuracion.get_bool('chk_config_ocultar_condiciones_pago'), 'cfdi_open_pdf': Configuracion.get_bool('chk_config_open_pdf'), @@ -2189,6 +2190,28 @@ class Alumnos(BaseModel): ) return tuple(rows) + @classmethod + def get_by_name(cls, values): + rows = () + name = values.get('name', '') + if name: + rows = (Alumnos + .select( + Alumnos.id, + Alumnos.nombre, + Alumnos.paterno, + Alumnos.materno, + Alumnos.rfc) + .where((Alumnos.es_activo==True) & + (Alumnos.nombre.contains(name) | + Alumnos.paterno.contains(name) | + Alumnos.materno.contains(name) | + Alumnos.rfc.contains(name))) + .dicts()) + rows = tuple(rows) + + return rows + @classmethod def get_by(cls, values): if 'id' in values: @@ -3208,8 +3231,9 @@ class Facturas(BaseModel): locales_retenciones = 0 for product in products: - # ~ print ('\n', product['descripcion']) + # ~ print ('\n', product) id_product = product.pop('id') + id_student = product.pop('id_student', 0) p = Productos.get(Productos.id==id_product) product['unidad'] = p.unidad.key @@ -3235,6 +3259,13 @@ class Facturas(BaseModel): descuento_cfdi += product['descuento'] subtotal += product['importe'] + if id_student: + student = Alumnos.get(Alumnos.id==id_student) + product['alumno'] = str(student) + product['curp'] = student.curp + product['nivel'] = student.grupo.nivel.nombre + product['autorizacion'] = student.grupo.nivel.autorizacion.strip() + FacturasDetalle.create(**product) base = product['importe'] - product['descuento'] @@ -3365,6 +3396,7 @@ class Facturas(BaseModel): emisor = Emisor.select()[0] certificado = Certificado.select()[0] + is_edu = False comprobante = {} relacionados = {} donativo = {} @@ -3438,6 +3470,16 @@ class Facturas(BaseModel): if row.pedimento: concepto['Pedimento'] = row.pedimento + if row.autorizacion: + is_edu = True + concepto['student'] = { + 'version': '1.0', + 'nombreAlumno': row.alumno, + 'CURP': row.curp, + 'nivelEducativo': row.nivel, + 'autRVOE': row.autorizacion, + } + taxes = {} traslados = [] retenciones = [] @@ -3564,6 +3606,7 @@ class Facturas(BaseModel): 'conceptos': conceptos, 'impuestos': impuestos, 'donativo': donativo, + 'edu': is_edu, 'complementos': complementos, } return util.make_xml(data, certificado, auth) diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 2bc88d4..fb26e80 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -22,6 +22,7 @@ var invoices_controllers = { $$('grid_clients_found').attachEvent('onValueSuggest', grid_clients_found_click) $$('search_product_id').attachEvent('onKeyPress', search_product_id_key_press) $$('grid_products_found').attachEvent('onValueSuggest', grid_products_found_click) + $$('grid_students_found').attachEvent('onValueSuggest', grid_students_found_click) $$('grid_details').attachEvent('onItemClick', grid_details_click) $$('grid_details').attachEvent('onHeaderClick', grid_details_header_click) $$('grid_details').attachEvent('onBeforeEditStart', grid_details_before_edit_start) @@ -161,6 +162,7 @@ function default_config(){ }else{ $$('tv_invoice').getTabbar().showOption('INE') } + cfg_invoice['edu'] = values.cfdi_edu cfg_invoice['open_pdf'] = values.cfdi_open_pdf cfg_invoice['tax_locales'] = values.cfdi_tax_locales cfg_invoice['tax_decimals'] = values.cfdi_tax_decimals @@ -168,6 +170,9 @@ function default_config(){ if(values.cfdi_show_pedimento){ $$('grid_details').showColumn('pedimento') } + if(values.cfdi_edu){ + $$('grid_details').showColumn('student') + } }) } @@ -557,6 +562,7 @@ function guardar_y_timbrar(values){ delete rows[i]['clave_sat'] delete rows[i]['unidad'] delete rows[i]['importe'] + delete rows[i]['student'] rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario']) rows[i]['descuento'] = parseFloat(rows[i]['descuento']) } @@ -893,6 +899,25 @@ function grid_products_found_click(obj){ } +function grid_students_found_click(obj){ + var form = $$('form_invoice') + var row = grid.getSelectedItem() + + if (row == undefined){ + msg = 'Selecciona un registro primero' + msg_error(msg) + return + } + + var values = { + id_student: obj.id, + student: obj.nombre + ' ' + obj.paterno + ' ' + obj.materno, + } + grid.updateItem(row.id, values) + form.setValues({search_student: ''}, true) +} + + function search_product_by_key(key){ webix.ajax().get('/values/productokey', {'key': key}, { error: function(text, data, xhr) { diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 754bf82..94252b1 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -285,6 +285,8 @@ var grid_details_cols = [ {id: "descripcion", header:{text: 'Descripción', css: 'center'}, fillspace: true, editor: 'popup'}, {id: "pedimento", header: 'Pedimento', editor: 'text', hidden: true}, + {id: "id_student", header: 'ID_Alumno', hidden: true}, + {id: 'student', header: 'Alumno', hidden: true, width: 150}, {id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100}, {id: 'cantidad', header: {text: 'Cantidad', css: 'center'}, width: 100, format: webix.i18n.numberFormat, css: 'right', editor: 'text'}, @@ -386,6 +388,31 @@ var suggest_products = { } +var suggest_students = { + view: 'gridsuggest', + id: 'grid_students_found', + name: 'grid_students_found', + body: { + autoConfig: false, + header: false, + columns: [ + {id: 'id', hidden: true}, + {id: 'nombre', adjust: 'data'}, + {id: 'paterno', adjust: 'data'}, + {id: 'materno', adjust: 'data'}, + {id: 'rfc', adjust: 'data'}, + ], + dataFeed:function(text){ + if (text.length > 2){ + this.load('/values/student?name=' + text) + }else{ + this.hide() + } + } + } +} + + var body_comprobante = {rows: [{ cols: [ { @@ -450,6 +477,13 @@ var body_regimen_fiscal = { } +var body_students = {rows:[ + {view: 'search', id: 'search_student', + name: "search_student", label: "por Nombre o RFC", + labelPosition:'top', suggest: suggest_students, + placeholder:'Captura al menos tres letras'}, +]} + var controls_generate = [ {minHeight: 10, maxHeight: 10}, toolbar_invoices_generate, @@ -484,7 +518,9 @@ var controls_generate = [ labelPosition:'top', suggest: suggest_products, placeholder:'Captura al menos tres letras'}, ]}, - ]}} + ]}}, + {view: 'fieldset', id: 'fs_students', label: 'Buscar Alumno', + body: body_students}, ]}, {maxWidth: 10}, {maxWidth: 300, rows: [