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/main.py b/source/app/controllers/main.py
index a342572..59cb9e1 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:
@@ -219,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/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/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 ad8f47c..84a9b78 100644
--- a/source/app/models/db.py
+++ b/source/app/models/db.py
@@ -25,6 +25,15 @@ 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()
+
+ def _get_niveduall(self, values):
+ return main.NivelesEducativos.get_all()
+
def _get_titlelogin(self, values):
return main.get_title_app(2)
@@ -244,11 +253,18 @@ class StorageEngine(object):
return main.Usuarios.remove(id)
if table == 'config':
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):
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)
@@ -291,6 +307,16 @@ 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'])
+ if opt == 'edit':
+ return main.Alumnos.actualizar(values['values'])
+
def tickets(self, values, user):
opt = values.pop('opt')
if opt == 'add':
@@ -338,6 +364,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..5d234c0 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
@@ -215,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'),
@@ -1401,6 +1403,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 +1423,32 @@ class NivelesEducativos(BaseModel):
def __str__(self):
return '{} ({})'.format(self.nombre, self.autorizacion)
+ @classmethod
+ def get_all(cls):
+ 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:
+ obj = NivelesEducativos.create(**values)
+ # Revisar
+ cls._add_group(cls, obj)
+ 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)
@@ -1430,6 +1464,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)
@@ -2128,6 +2173,91 @@ 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_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:
+ id = int(values['id'])
+ w = (Alumnos.id==id)
+ rows = cls._get(cls, w)
+ return rows[0]
+
+ 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
+
+ @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)
@@ -3101,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
@@ -3128,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']
@@ -3258,6 +3396,7 @@ class Facturas(BaseModel):
emisor = Emisor.select()[0]
certificado = Certificado.select()[0]
+ is_edu = False
comprobante = {}
relacionados = {}
donativo = {}
@@ -3331,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 = []
@@ -3457,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/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/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/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..5e99840
--- /dev/null
+++ b/source/static/js/controller/school.js
@@ -0,0 +1,154 @@
+
+
+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 form = this.getFormView();
+
+ if (!form.validate()) {
+ msg_error(msg)
+ return
+ }
+
+ var values = form.getValues();
+ opt = 'add'
+ if(values.id){
+ opt = 'edit'
+ }
+
+ 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)
+ },
+ 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/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/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: [
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..002dfc4
--- /dev/null
+++ b/source/static/js/ui/school.js
@@ -0,0 +1,147 @@
+
+
+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}},
+ {}]},
+ {cols: [
+ {view: 'richselect', id: 'student_grupo', name: 'grupo',
+ label: 'Nivel Educativo: ', required: true, options: [],
+ invalidMessage: 'El Nivel Educativo es requerido'},
+ {},
+ ]},
+]
+
+
+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 @@
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+