Form login

This commit is contained in:
Mauricio Baeza 2017-06-27 15:43:02 -05:00
parent 847b5a560c
commit 59179a17b9
47 changed files with 59487 additions and 1 deletions

11
.gitignore vendored
View File

@ -6,6 +6,15 @@ __pycache__/
# Django stuff:
*.log
local_settings.py
conf.py
source/fixtures
source/media
# Sphinx documentation
docs/_build/
docs/
*.ods
*.xlsx
credenciales.conf
*.sqlite
*.sql

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
falcon
Beaker
Mako
peewee
logbook
bcrypt

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
from peewee import SqliteDatabase
DEBUG = True
ID_SUPPORT = ''
DATABASE = None
if DEBUG:
DATABASE = SqliteDatabase('empresalibre.sqlite')

12
source/app/main.ini Normal file
View File

@ -0,0 +1,12 @@
[uwsgi]
socket = 127.0.0.1:3032
uid = nginx
gid = nginx
chdir = /srv/app/empresalibre/app
wsgi-file = main.py
callable = app
master = true
processes = 4
threads = 4
#~ stats = 127.0.0.1:9191
logger = file:/srv/log/empresalibre-uwsgi.log

123
source/app/main.py Normal file
View File

@ -0,0 +1,123 @@
#!/usr/bin/env python3
import falcon
from beaker.middleware import SessionMiddleware
import util
import middleware as MW
from models import StorageEngine, get_cp
from settings import DEBUG, WITH_LOGIN
class AppPostalCode(object):
def on_get(self, req, resp):
values = req.params
req.context['result'] = get_cp(values['cp'])
resp.status = falcon.HTTP_200
class AppLogin(object):
template = 'login.html'
def __init__(self, db):
self._db = db
@falcon.after(MW.get_template)
def on_get(self, req, resp):
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
session = req.env['beaker.session']
values = req.params
result = self._db.authenticate(values['user'], values['pass'])
resp.body = util.dumps(result)
if result['login']:
session.save()
session['user'] = result['user']
resp.status = falcon.HTTP_200
class AppLogout(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
session = req.env['beaker.session']
session.delete()
resp.status = falcon.HTTP_200
raise falcon.HTTPTemporaryRedirect('/')
class AppMain(object):
template = 'main.html'
def __init__(self, db):
self._db = db
@falcon.after(MW.get_template)
def on_get(self, req, resp):
resp.status = falcon.HTTP_200
class AppPartners(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_partners(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
if values['id'] == '0':
del values['id']
req.context['result'] = self._db.new_partner(values)
else:
req.context['result'] = self._db.update_partner(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.delete_partner(values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
db = StorageEngine()
pc = AppPostalCode()
login = AppLogin(db)
logout = AppLogout(db)
main = AppMain(db)
partners = AppPartners(db)
api = falcon.API(middleware=[MW.AuthMiddleware(), MW.JSONTranslator()])
api.req_options.auto_parse_form_urlencoded = True
api.add_sink(MW.handle_404, '')
if WITH_LOGIN:
api.add_route('/', login)
api.add_route('/logout', logout)
api.add_route('/main', main)
api.add_route('/partners', partners)
api.add_route('/cp', pc)
if DEBUG:
api.add_sink(MW.static, '/static')
# Configure the SessionMiddleware
session_opts = {
'session.type': 'file',
'session.cookie_expires': True,
'session.data_dir': '/tmp/cache/data',
'session.lock_dir': '/tmp/cache/lock',
#~ 'session.httponly': True,
#~ 'session.secure': True,
}
app = SessionMiddleware(api, session_opts)

View File

@ -0,0 +1,8 @@
[uwsgi]
http = 127.0.0.1:8000
wsgi-file = main.py
callable = app
master = true
processes = 4
threads = 4
py-autoreload = 1

50
source/app/middleware.py Normal file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env python
import falcon
import util
from settings import PATH_STATIC
def handle_404(req, resp):
id_session = req.cookies.get('beaker.session.id', '')
if id_session:
raise falcon.HTTPTemporaryRedirect('/main')
raise falcon.HTTPTemporaryRedirect('/')
def get_template(req, resp, resource):
session = req.env['beaker.session']
resp.content_type = 'text/html'
data = {'username': session.get('user', '')}
resp.body = util.get_template(resource.template, data)
def static(req, res):
path = PATH_STATIC + req.path
if util.is_file(path):
res.content_type = util.get_mimetype(path)
res.stream, res.stream_len = util.get_stream(path)
res.status = falcon.HTTP_200
else:
res.status = falcon.HTTP_404
class AuthMiddleware(object):
#~ def process_request(self, req, resp):
def process_resource(self, req, resp, resource, params):
paths = ('/main', '/partners', '/cp')
id_session = req.cookies.get('beaker.session.id', '')
if req.path in paths and not id_session:
raise falcon.HTTPTemporaryRedirect('/')
class JSONTranslator(object):
def process_response(self, req, resp, resource):
if 'result' not in req.context:
return
resp.body = util.dumps(req.context['result'])

263
source/app/models.py Normal file
View File

@ -0,0 +1,263 @@
#!/usr/bin/env python
import sys
import util
from peewee import *
from conf import DATABASE
from settings import log
database = DATABASE
class BaseModel(Model):
class Meta:
database = database
class Users(BaseModel):
username = CharField(max_length=50, unique=True)
first_name = CharField(max_length=50, default='')
last_name = CharField(max_length=50, default='')
email = CharField(max_length=200, default='')
password = CharField(max_length=100)
is_superuser = BooleanField(default=False)
is_admin = BooleanField(default=False)
is_staff = BooleanField(default=False)
is_active = BooleanField(default=True)
date_joined = DateTimeField(default=util.now)
last_login = DateTimeField(null=True)
def __str__(self):
t = '{} {} ({})'
return t.format(self.first_name, self.last_name, self.username)
class Meta:
order_by = ('username',)
class Clients(BaseModel):
id = IntegerField(db_column='cliente_id', primary_key=True)
cia = CharField(max_length=11, default='', index=True)
rfc = CharField(max_length=13, index=True)
name = CharField(db_column='nombre', max_length=60)
street = CharField(db_column='calle', max_length=60, default='')
num_ext = CharField(db_column='noExterior', max_length=10, default='')
num_int = CharField(db_column='noInterior', max_length=10, default='')
colonia = CharField(max_length=60, default='')
localidad = CharField(max_length=60, default='')
municipio = CharField(max_length=60, default='')
state = CharField(db_column='estado', max_length=40, default='')
country = CharField(db_column='pais', max_length=30, default='')
postal_code = CharField(db_column='codigoPostal', max_length=6, default='')
email = CharField(db_column='correos', max_length=250, default='')
contact = CharField(db_column='nombre_contacto', max_length=60, default='')
phone = CharField(db_column='telefono', max_length=40, default='')
CUS_ErpId = FixedCharField(max_length=20, default='')
CUS_SatClas = FixedCharField(max_length=2, default='')
CUS_SatClasNombre = FixedCharField(max_length=50, default='')
CUS_DefaultCurrency = FixedCharField(max_length=3, default='')
CUS_DefaultCurrencyNombre = FixedCharField(max_length=120, default='')
CUS_AccHabe = FixedCharField(max_length=20, default='')
CUS_AccHabeNombre = FixedCharField(max_length=60, default='')
CUS_AccDebe = FixedCharField(max_length=20, default='')
CUS_AccDebeNombre = FixedCharField(max_length=60, default='')
def __str__(self):
t = '{} ({})'
return t.format(self.name, self.rfc)
class Meta:
db_table = 'clientes'
order_by = ('name',)
class StorageEngine(object):
def __init__(self):
database.connect()
def authenticate(self, username, password):
data = {'login': False, 'msg': 'No Autorizado', 'user': ''}
try:
obj = Users.get(Users.username==username, Users.is_active==True)
except Users.DoesNotExist:
return data
if not util.validate_password(obj.password, password):
return data
obj.last_login = util.now()
obj.save()
data['msg'] = ''
data['login'] = True
data['user'] = str(obj)
return data
def get_partners(self, values):
if values:
id = int(values['id'])
row = Clients.select().where(Clients.id==id).dicts()[0]
return row
else:
rows = Clients.select(
Clients.id, Clients.cia, Clients.rfc, Clients.name).dicts()
return {'ok': True, 'rows': tuple(rows)}
def new_partner(self, values):
fields = util.clean(values)
fields['rfc'] = fields['rfc'].upper()
Clients.create(**fields)
#~ Wend custom ID
obj = Clients.select().order_by(Clients.id.desc()).get()
row = {
'id': obj.id,
'cia': obj.cia,
'rfc': obj.rfc,
'name': obj.name,
}
data = {'ok': True, 'row': row, 'new': True}
return data
def update_partner(self, values):
id = int(values['id'])
del values['id']
fields = util.clean(values)
fields['rfc'] = fields['rfc'].upper()
q = Clients.update(**values).where(Clients.id==id)
q.execute()
row = {
'id': id,
'cia': fields['cia'],
'rfc': fields['rfc'],
'name': fields['name'],
}
data = {'ok': True, 'row': row, 'new': False}
return data
def delete_partner(self, id):
q = Clients.delete().where(Clients.id==id)
return bool(q.execute())
def get_cp(cp):
data = {}
rows = util.get_cp(cp)
if rows:
data = {
'estado': rows[0][2],
'municipio': rows[0][1],
}
if len(rows) == 1:
data['colonia'] = rows[0][0]
else:
data['colonia'] = [r[0] for r in rows]
#~ print ('DATA', data)
return data
def _connect():
global database
log.info('Connect to DB...')
database.connect()
return
def create_tables():
_connect()
tables = [Users, Clients]
database.create_tables(tables, True)
log.info('Create tables OK...')
return
def migrate_tables():
_connect()
log.info('Migrate tables OK...')
return
def create_superuser(username):
_connect()
ok, password = util.get_pass()
if not ok:
log.error(password)
return
try:
obj = Users.create(username=username, password=password, is_superuser=True)
except IntegrityError:
msg = 'El usuario ya existe'
log.error(msg)
return
log.info('Create super user OK...')
return
def change_password(username):
_connect()
try:
obj = Users.get(username=username)
except Users.DoesNotExist:
msg = 'El usuario no existe'
log.error(msg)
return
ok, password = util.get_pass()
if not ok:
log.error(password)
return
obj.password = password
obj.save()
log.info('Change password OK...')
return
def main(opt, username=''):
if opt == '-c':
create_tables()
elif opt == '-m':
migrate_tables()
elif opt == '-su':
create_superuser(username)
elif opt == '-cc':
change_password(username)
return
if __name__ == '__main__':
opt = sys.argv
if len(opt) == 1:
msg = 'Agrega un argumento'
log.error(msg)
sys.exit(0)
ov = ('-h', '-c', '-m', '-su', '-cc')
if not opt[1] in ov:
msg = 'Opción no válida. Usa -h para ver las opciones'
log.error(msg)
sys.exit(0)
if opt[1] == '-h':
msg = '-h Muestra esta ayuda.\n\t' \
'-c Crea tablas.\n\t' \
'-m Migra tablas.\n\t' \
'-su USERNAME Crea Super Usuario.\n\t' \
'-cc USERNAME Cambiar contraseña.\n\t'
log.info(msg)
sys.exit(0)
if opt[1] == '-su' or opt[1] == '-cc' :
if len(opt) == 2:
msg = 'Falta el argumento: nombre de usuario'
log.error(msg)
sys.exit(0)
try:
main(opt[1], opt[2])
sys.exit(0)
except KeyboardInterrupt:
msg = 'La contraseña es necesaria'
log.error(msg)
sys.exit(0)
main(opt[1])

52
source/app/settings.py Normal file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python
import logbook
import os
import sys
from mako.lookup import TemplateLookup
from logbook import Logger, StreamHandler, RotatingFileHandler
logbook.set_datetime_format("local")
from conf import DEBUG
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..'))
PATH_TEMPLATES = os.path.abspath(os.path.join(BASE_DIR, '..', 'templates'))
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
WITH_LOGIN = True
DB_CP = 'cp.db'
PATH_CP = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', DB_CP))
template_lookup = TemplateLookup(directories=[PATH_TEMPLATES],
input_encoding='utf-8',
output_encoding='utf-8')
LOG_PATH = 'empresalibre.log'
LOG_NAME = 'API'
LOG_LEVEL = 'INFO'
format_string = '[{record.time:%d-%b-%Y %H:%M:%S}] ' \
'{record.level_name}: ' \
'{record.channel}: ' \
'{record.message}'
if DEBUG:
LOG_LEVEL = 'DEBUG'
StreamHandler(
sys.stdout,
level=LOG_LEVEL,
format_string=format_string).push_application()
else:
LOG_PATH = '/srv/log/empresalibre.log'
RotatingFileHandler(
LOG_PATH,
backup_count=10,
max_size=1073741824,
level=LOG_LEVEL,
format_string=format_string).push_application()
log = Logger(LOG_NAME)

99
source/app/util.py Normal file
View File

@ -0,0 +1,99 @@
#!/usr/bin/env python
import datetime
import getpass
import json
import mimetypes
import os
import sqlite3
import uuid
import bcrypt
from settings import template_lookup, PATH_CP
def _get_hash(password):
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
def validate_password(hashed, password):
return bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode()
def get_pass():
password = getpass.getpass('Introduce la contraseña: ')
pass2 = getpass.getpass('Confirma la contraseña: ')
if password != pass2:
msg = 'Las contraseñas son diferentes'
return False, msg
password = password.strip()
if not password:
msg = 'La contraseña es necesaria'
return False, msg
return True, _get_hash(password)
def now():
return datetime.datetime.now()
def get_token():
return _get_hash(uuid.uuid4().hex)
def get_mimetype(path):
mt = mimetypes.guess_type(path)[0]
return mt or 'application/octet-stream'
def is_file(path):
return os.path.isfile(path)
def get_stream(path):
return get_file(path), get_size(path)
def get_file(path):
return open(path, 'rb')
def get_size(path):
return os.path.getsize(path)
def get_template(name, data={}):
#~ print ('NAME', name, data)
template = template_lookup.get_template(name)
return template.render(**data)
def dumps(data):
return json.dumps(data, default=str)
def clean(values):
for k, v in values.items():
if isinstance(v, str):
values[k] = v.strip()
return values
def get_cp(cp):
con = sqlite3.connect(PATH_CP)
cursor = con.cursor()
sql = """
SELECT colonia, municipio, estado
FROM colonias, municipios, estados
WHERE colonias.id_municipio=municipios.id
AND municipios.id_estado=estados.id
AND cp=?
ORDER BY colonia"""
cursor.execute(sql, (cp,))
data = cursor.fetchall()
cursor.close()
con.close()
return data

BIN
source/db/cp.db Normal file

Binary file not shown.

1403
source/static/css/air.css Normal file

File diff suppressed because it is too large Load Diff

42
source/static/css/app.css Normal file
View File

@ -0,0 +1,42 @@
.login_header {
text-align: center;
font-weight: bold;
font-size: 150%;
font-family: Arial, Helvetica, sans-serif;
}
.cmd_edit_parter {
max-width: 32px,
padding-left: 5px;
}
.center {
text-align: center;
}
.right {
text-align: right;
}
.cmd_close_partner div button {
background-color: red !important;
border-color: red !important;
border-bottom: 1px solid red !important;
}
/*
.webix_message_area {
position: absolute;
margin-top: 25%;
left: 50%;
transform: translate(-50%, -50%);
width: 40%;
}
*/
.webix_success div {
background-color: #00a65a !important;
font-size: 1vw;
color: white;
}

View File

@ -0,0 +1,95 @@
/* SideBar*/
.webix_sidebar{
background: #ECEFF1;
}
.webix_sidebar .webix_tree_item {
color: #454545;
height: 35px;
line-height: 35px;
}
.webix_sidebar .webix_scroll_cont > .webix_tree_leaves {
padding: 0;
}
.webix_sidebar .webix_tree_leaves .webix_tree_leaves {
margin-left: 0px;
}
.webix_sidebar_selected,
.webix_sidebar_expanded .webix_tree_item:hover{
background-color: rgba(0,0,0,0.02);
}
.webix_sidebar .webix_tree_item.webix_selected,
.webix_sidebar .webix_tree_item.webix_selected span{
background-color: #27ae60;
padding-right:0;
}
.webix_sidebar .webix_tree_branch_1 .webix_tree_item{
padding-left:40px;
}
.webix_sidebar .webix_tree_branch_1>.webix_tree_item{
height: 40px;
line-height: 40px;
padding-left:0;
}
.webix_sidebar .webix_tree_branch_1{
border-bottom:1px solid #e5e5e5;
}
.webix_sidebar .webix_tree_item.webix_selected span,
.webix_sidebar .webix_tree_item span{
margin:0;
padding:0px;
}
.webix_sidebar_icon{
width: 40px;
text-align: center;
}
.webix_sidebar_dir_icon{
float: right;
line-height: inherit;
}
/*SubMenu (Popup) */
.webix_sidebar_popup{
border:none !important;
box-shadow: 2px 3px 3px #ddd;
}
.webix_sidebar_popup, .webix_sidebar_popup .webix_list_item{
border-radius:0;
}
.webix_sidebar_popup_right{
box-shadow: -1px 3px 3px #ddd;
}
/*SubMenu: title*/
.webix_sidebar_popup_title{
background: #ECEFF1;
}
.webix_sidebar_popup_title.webix_selected{
border-left-color: #27ae60;
background: #27ae60;
}
.webix_sidebar_popup_title .webix_template{
line-height: 40px;
padding: 0 10px;
border: 1px solid #E5E5E5;
border-left:none;
}
.webix_sidebar_selected.webix_sidebar_popup_title .webix_template{
background: rgba(0,0,0,0.03);
border-left: none;
}
.webix_sidebar_popup_list .webix_list_item{
border-left: 1px solid #E5E5E5;
border-right: 1px solid #E5E5E5;
}
/*SubMenu: list*/
.webix_sidebar_popup_list .webix_list_item:first-child{
border-top: 1px solid #E5E5E5;
}
.webix_sidebar_popup_list .webix_list_item:hover{
background: #f6f9fb;
}
.webix_sidebar_popup_list .webix_list_item.webix_selected:hover{
background: #27ae60;
}

View File

@ -0,0 +1,94 @@
/* SideBar*/
.webix_sidebar{
background: #ECEFF1;
}
.webix_sidebar .webix_tree_item {
color: #454545;
height: 35px;
line-height: 35px;
}
.webix_sidebar .webix_scroll_cont > .webix_tree_leaves {
padding: 0;
}
.webix_sidebar .webix_tree_leaves .webix_tree_leaves {
margin-left: 0px;
}
.webix_sidebar_selected,
.webix_sidebar_expanded .webix_tree_item:hover{
background-color: rgba(0,0,0,0.02);
}
.webix_sidebar .webix_tree_item.webix_selected,
.webix_sidebar .webix_tree_item.webix_selected span{
background-color: #ffdb8f;
padding-right:0;
}
.webix_sidebar .webix_tree_branch_1 .webix_tree_item{
padding-left:40px;
}
.webix_sidebar .webix_tree_branch_1>.webix_tree_item{
height: 40px;
line-height: 40px;
padding-left:0;
}
.webix_sidebar .webix_tree_branch_1{
border-bottom:1px solid #e5e5e5;
}
.webix_sidebar .webix_tree_item.webix_selected span,
.webix_sidebar .webix_tree_item span{
margin:0;
padding:0px;
}
.webix_sidebar_icon{
width: 40px;
text-align: center;
}
.webix_sidebar_dir_icon{
float: right;
line-height: inherit;
}
/*SubMenu (Popup) */
.webix_sidebar_popup{
border:none !important;
box-shadow: 2px 3px 3px #ddd;
}
.webix_sidebar_popup, .webix_sidebar_popup .webix_list_item{
border-radius:0;
}
.webix_sidebar_popup_right{
box-shadow: -1px 3px 3px #ddd;
}
/*SubMenu: title*/
.webix_sidebar_popup_title{
background: #ECEFF1;
}
.webix_sidebar_popup_title.webix_selected{
border-left-color: #27ae60;
background: #27ae60;
}
.webix_sidebar_popup_title .webix_template{
line-height: 40px;
padding: 0 10px;
border: 1px solid #E5E5E5;
border-left:none;
}
.webix_sidebar_selected.webix_sidebar_popup_title .webix_template{
background: rgba(0,0,0,0.03);
border-left: none;
}
.webix_sidebar_popup_list .webix_list_item{
border-left: 1px solid #E5E5E5;
border-right: 1px solid #E5E5E5;
}
/*SubMenu: list*/
.webix_sidebar_popup_list .webix_list_item:first-child{
border-top: 1px solid #E5E5E5;
}
.webix_sidebar_popup_list .webix_list_item:hover{
background: #f6f9fb;
}
.webix_sidebar_popup_list .webix_list_item.webix_selected:hover{
background: #27ae60;
}

View File

@ -0,0 +1,108 @@
/* SideBar*/
.webix_sidebar{
background: #fff;
}
.webix_sidebar .webix_tree_item {
color: #454545;
height: 35px;
line-height: 35px;
}
.webix_sidebar .webix_scroll_cont > .webix_tree_leaves {
padding: 0;
}
.webix_sidebar_selected{
background-color: #f2f2f2;
}
.webix_sidebar_expanded .webix_tree_item:hover{
background-color: #F6F6F6;
}
.webix_sidebar .webix_tree_item.webix_selected{
background-color: #F6F6F6;
box-shadow: 5px 0 #3498db inset;
}
.webix_sidebar .webix_tree_item.webix_selected span{
background-color: transparent;
padding-right:0;
}
.webix_sidebar .webix_tree_branch_1 .webix_tree_item{
padding-left:40px;
}
.webix_sidebar .webix_tree_branch_1>.webix_tree_item{
height: 40px;
line-height: 40px;
padding-left:0;
}
.webix_sidebar .webix_tree_branch_1{
border-bottom:1px solid #f5f5f5;
}
.webix_sidebar .webix_tree_item.webix_selected span, .webix_sidebar .webix_tree_item span{
margin:0;
padding:0px;
}
.webix_sidebar_icon{
width: 40px;
text-align: center;
}
.webix_sidebar .webix_tree_leaves .webix_tree_leaves {
margin-left: 0px;
}
.webix_sidebar_dir_icon{
float: right;
line-height: inherit;
}
/*Popup*/
.webix_sidebar_popup{
padding:0 !important;
border-radius: 0 !important;
margin-top: 0 !important;
border:none !important;
box-shadow: 2px 3px 3px #ddd;
}
.webix_sidebar_popup_right{
box-shadow: -1px 3px 3px #ddd;
}
.webix_sidebar_popup, .webix_sidebar_popup .webix_list_item,
.webix_sidebar_popup .webix_win_body, .webix_win_body > .webix_view,
.webix_sidebar_popup .webix_win_content{
border-radius:0;
}
.webix_sidebar_popup_title.webix_selected .webix_template{
background-color: #F6F6F6;
}
.webix_sidebar_popup_title .webix_template{
line-height: 40px;
padding: 0 10px;
border: 1px solid #f5f5f5;
border-bottom-color: #ddd;
border-left:none;
}
.webix_sidebar_selected.webix_sidebar_popup_title .webix_template{
background: #f2f2f2;
border-left: none;
}
.webix_sidebar_popup_list .webix_list_item{
border-left: 1px solid #f2f2f2;
border-right: 1px solid #f5f5f5;
}
.webix_sidebar_popup_list .webix_list_item:first-child{
border-top: 1px solid #f5f5f5;
}
.webix_sidebar_popup_list .webix_list_item:hover{
background-color: #F6F6F6;
}
.webix_sidebar_popup_list .webix_list_item.webix_selected,
.webix_sidebar_popup_list .webix_list_item.webix_selected:hover{
background-color: #F6F6F6;
box-shadow: 5px 0 #3498db inset;
}

8683
source/static/css/webix.css Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,14 @@
## Font Awesome
Copyright (C) 2012 by Dave Gandy
Author: Dave Gandy
License: SIL (http://scripts.sil.org/OFL)
Homepage: http://fortawesome.github.com/Font-Awesome/
## PT Sans Free
Copyright © 2009 ParaType Ltd.
License: Free Font License v1.00

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

View File

@ -0,0 +1,44 @@
function cmd_new_invoice_click(id, e, node){
$$('form_invoice').adjust();
$$("multi_invoices").setValue("invoices_new")
};
function cmd_edit_invoice_click(id, e, node){
$$("multi_invoices").setValue("invoices_new")
};
function cmd_delete_invoice_click(id, e, node){
webix.message({type:"success", text: "OK Delete"});
};
function cmd_save_invoice_click(id, e, node){
var form = this.getFormView();
if (!form.validate()) {
webix.message({ type:"error", text:"Valores inválidos" });
return
}
$$('form_invoice').clear();
$$("multi_invoices").setValue("invoices_home")
webix.message({type:"success", text: "Factura guardada correctamente"});
};
function cmd_cancel_invoice_click(id, e, node){
$$("multi_invoices").setValue("invoices_home")
};

View File

@ -0,0 +1,54 @@
var controllers = {
init: function(){
//~ Main
$$("menu_user").attachEvent("onMenuItemClick", menu_user_click);
//~ Partner
$$("cmd_new_partner").attachEvent("onItemClick", cmd_new_partner_click);
$$("cmd_edit_partner").attachEvent("onItemClick", cmd_edit_partner_click);
$$("cmd_delete_partner").attachEvent("onItemClick", cmd_delete_partner_click);
$$("cmd_save_partner").attachEvent("onItemClick", cmd_save_partner_click);
$$("cmd_cancel_partner").attachEvent("onItemClick", cmd_cancel_partner_click);
//~ $$('grid_partners').attachEvent('onItemClick', grid_partners_click);
$$('postal_code').attachEvent('onKeyPress', postal_code_key_press);
$$('postal_code').attachEvent('onTimedKeyPress', postal_code_key_up);
$$('colonia').attachEvent('onFocus', colonia_on_focus)
//~ Products
$$("cmd_new_product").attachEvent("onItemClick", cmd_new_product_click);
$$("cmd_edit_product").attachEvent("onItemClick", cmd_edit_product_click);
$$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click);
$$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click);
$$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click);
//~ Invoices
$$("cmd_new_invoice").attachEvent("onItemClick", cmd_new_invoice_click);
$$("cmd_edit_invoice").attachEvent("onItemClick", cmd_edit_invoice_click);
$$("cmd_delete_invoice").attachEvent("onItemClick", cmd_delete_invoice_click);
$$("cmd_save_invoice").attachEvent("onItemClick", cmd_save_invoice_click);
$$("cmd_cancel_invoice").attachEvent("onItemClick", cmd_cancel_invoice_click);
get_partners()
}
};
function get_partners(){
webix.ajax().get("/partners", {}, {
error: function(text, data, xhr) {
webix.message({ type:"error", text: "Error al consultar"});
},
success: function(text, data, xhr) {
var values = data.json();
$$("grid_partners").clearAll();
if (values.ok){
$$("grid_partners").parse(values.rows, 'json');
};
}
});
}
function menu_user_click(id, e, node){
if (id == 1){
window.location = '/logout';
return
}
}

View File

@ -0,0 +1,167 @@
function cmd_new_partner_click(id, e, node){
$$('form_partner').setValues({id:0, country:'México'});
$$('grid_partners').clearSelection();
$$("multi_partners").setValue("partners_new");
};
function cmd_edit_partner_click(id, e, node){
var row = $$('grid_partners').getSelectedItem();
if (row == undefined){
webix.message({ type:'error', text:'Selecciona un Cliente' });
return
};
webix.ajax().get("/partners", {id:row['id']}, {
error: function(text, data, xhr) {
webix.message({ type:"error", text: "Error al consultar"});
},
success: function(text, data, xhr){
var values = data.json();
$$('form_partner').setValues(values);
}
});
$$("multi_partners").setValue("partners_new")
};
function cmd_delete_partner_click(id, e, node){
var row = $$('grid_partners').getSelectedItem();
if (row == undefined){
webix.message({ type:'error', text:'Selecciona un Cliente' });
return
};
var msg = '¿Estás seguro de eliminar al cliente?<BR><BR>'
msg += row['name'] + ' (' + row['rfc'] + ')'
msg += '<BR><BR>ESTA ACCIÓN NO SE PUEDE DESHACER'
webix.confirm({
title:'Eliminar Cliente',
ok:'Si',
cancel:'No',
type:'confirm-error',
text:msg,
callback:function(result){
if (result){
delete_partner(row['id'])
}
}
})
};
function delete_partner(id){
webix.ajax().del('/partners', {id:id}, function(text, xml, xhr){
var msg = 'Cliente eliminado correctamente'
if (xhr.status == 200){
$$('grid_partners').remove(id);
webix.message({type:'success', text:msg});
} else {
msg = 'No se pudo eliminar'
webix.message({type:'error', text:msg});
}
});
}
function cmd_save_partner_click(id, e, node){
var form = this.getFormView();
if (!form.validate()) {
webix.message({ type:'error', text:'Valores inválidos' });
return
}
var values = form.getValues();
webix.ajax().post("partners", values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico';
webix.message({type:'error', text:msg});
},
success:function(text, data, XmlHttpRequest){
var values = data.json();
if (values.ok) {
update_grid_partner(values)
} else {
webix.message({type:'error', text:values.msg});
}
}
});
};
function update_grid_partner(values){
var msg = 'Cliente agregado correctamente'
if (values.new){
$$('form_partner').clear()
$$('grid_partners').add(values.row)
}else{
msg = 'Cliente actualizado correctamente'
$$("grid_partners").updateItem(values.row['id'], values.row)
}
$$('multi_partners').setValue('partners_home')
webix.message({type:'success', text:msg})
}
function cmd_cancel_partner_click(id, e, node){
$$('multi_partners').setValue('partners_home')
};
function postal_code_key_up(){
var value = this.getValue()
var msg = ''
if( value.length == 5 ){
webix.ajax().get('/cp', {cp:value}, {
error: function(text, data, xhr) {
webix.message({type:'error', text:'Error al consultar el C.P.'})
},
success: function(text, data, xhr) {
var values = data.json();
if (values.estado == undefined){
msg = 'No se encontró el C.P., asegurate de que sea correcto'
webix.message({type:'error', text:msg})
} else {
$$('form_partner').setValues({
state:values.estado,
municipio: values.municipio,
colonia:''}, true)
$$('colonia').define('suggest', [])
if (webix.isArray(values.colonia)){
$$('colonia').define('suggest', values.colonia)
}else{
$$('form_partner').setValues({colonia:values.colonia}, true)
}
$$('colonia').refresh()
$$('form_partner').focus('colonia')
}
}
})
}
}
function postal_code_key_press(code, e){
var data = [8, 9, 37, 39, 46]
if ( data.indexOf(code) >= 0 ){
return true;
}
if ( code < 48 || code > 57){
return false;
}
}
function colonia_on_focus(){
if ($$(this.config.suggest).getList().config.height > 2){
$$(this.config.suggest).show(this.getInputNode())
}
}

View File

@ -0,0 +1,43 @@
function cmd_new_product_click(id, e, node){
$$("multi_products").setValue("product_new")
};
function cmd_edit_product_click(id, e, node){
$$("multi_products").setValue("product_new")
};
function cmd_delete_product_click(id, e, node){
webix.message({type:"success", text: "OK Delete"});
};
function cmd_save_product_click(id, e, node){
var form = this.getFormView();
if (!form.validate()) {
webix.message({ type:"error", text:"Valores inválidos" });
return
}
$$('form_product').clear();
$$("multi_products").setValue("products_home")
webix.message({type:"success", text: "Producto guardado correctamente"});
};
function cmd_cancel_product_click(id, e, node){
$$("multi_products").setValue("products_home")
};

View File

@ -0,0 +1,64 @@
function validate_rfc(value){
rfc = value.trim().toUpperCase();
if ( rfc == ""){
webix.message({ type:"error", text:"El RFC no puede estar vacío" });
return false
}
if (rfc.length < 12 || rfc.length > 13){
webix.message({ type:"error", text:"Longitud incorrecta del RFC" });
return false
}
var start = 4;
//~ var tipo = $$("opt_tipo").getValue();
//~ if (tipo == 1 && value.length != 13){
//~ webix.message({ type:"error", text:"RFC debe ser de 13 caracteres" });
//~ return false
//~ }
//~ if (tipo == 2 && value.length != 12){
//~ webix.message({ type:"error", text:"RFC debe ser de 12 caracteres" });
//~ return false
//~ }
//~ var tipo = 1;
//~ if (tipo == 2){
//~ start = 3;
//~ }
//~ var rfc = value.toUpperCase();
if (rfc.length == 12){
start = 3;
}
var part = rfc.slice(0, start);
var re = new RegExp('[a-z&Ñ]{' + start + '}', 'i');
if (!part.match(re)){
webix.message({ type:"error", text: "El RFC tiene caractéres inválidos al inicio" });
return false
}
part = rfc.slice(-3);
re = new RegExp('[a-z0-9]{3}', 'i');
if (!part.match(re)){
webix.message({ type:"error", text: "El RFC tiene caractéres inválidos al final" });
return false
}
part = rfc.slice(-9, -3);
re = new RegExp('[0-9]{6}', 'i');
if (!part.match(re)){
webix.message({ type:"error", text: "Fecha inválida" });
return false
}
var month = parseInt(part.slice(-4, -2))
if (month == 0 || month > 12 ){
webix.message({ type:"error", text: "Fecha inválida" });
return false
}
var day = parseInt(part.slice(-2))
if (day == 0 || day > 31 ){
webix.message({ type:"error", text: "Fecha inválida" });
return false
}
return true
};

26
source/static/js/es-MX.js Normal file
View File

@ -0,0 +1,26 @@
/*Spanish (Mexico) locale*/
webix.i18n.locales["es-MX"] = {
groupDelimiter:",",
groupSize:3,
decimalDelimiter:".",
decimalSize:2,
dateFormat:"%d/%n/%Y",
timeFormat:"%h:%i %a",
longDateFormat:"%l, %d' %je '%F' %je '%Y",
fullDateFormat:"%l, %d' %je '%F' %je '%Y %h:%i %a",
am:["a.m.","A.M."],
pm:["p.m.","P.M."],
price:"${obj}",
priceSettings:{
groupDelimiter:",",
groupSize:3,
decimalDelimiter:".",
decimalSize:2
},
calendar:{
monthFull:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],
monthShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],
dayFull:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],
dayShort:["dom","lun","mar","mié","jue","vie","sáb"]
}
};

26
source/static/js/es.js Normal file
View File

@ -0,0 +1,26 @@
/*Spanish locale*/
webix.i18n.locales["es"] = {
groupDelimiter:".",
groupSize:3,
decimalDelimiter:",",
decimalSize:2,
dateFormat:"%d/%n/%Y",
timeFormat:"%G:%i",
longDateFormat:"%l, %d' %je '%F' %je '%Y",
fullDateFormat:"%l, %d' %je '%F' %je '%Y %G:%i",
am:null,
pm:null,
price:"{obj} €",
priceSettings:{
groupDelimiter:".",
groupSize:3,
decimalDelimiter:",",
decimalSize:2
},
calendar:{
monthFull:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],
monthShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],
dayFull:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],
dayShort:["dom","lun","mar","mié","jue","vie","sáb"]
}
};

275
source/static/js/sidebar.js Normal file
View File

@ -0,0 +1,275 @@
webix.protoUI({
name: "sidebar",
defaults:{
titleHeight: 40,
type: "sideBar",
activeTitle: true,
select: true,
scroll: false,
collapsed: false,
collapsedWidth: 41,
position: "left",
width: 250,
mouseEventDelay: 10
},
$init: function(config){
this.$ready.push(this._initSidebar);
this.$ready.push(this._initContextMenu);
},
on_context:{},
on_mouse_move:{},
_initSidebar: function(){
this._fullWidth = this.config.width;
this.attachEvent("onBeforeOpen", function(id){
if(!this.config.multipleOpen)
this.closeAll();
return !this.config.collapsed;
});
this.attachEvent("onItemClick", function(id, ev, node){
if(this.getPopup() && !this.getPopup().config.hidden)
ev.showpopup = true;
if(webix.env.touch)
this._showPopup(id, node);
});
this.attachEvent("onBeforeSelect", function(id){
if(!this.getItem(id).$count){
var selected = this.getSelectedId();
if(selected && id!= selected){
var parentId = this.getParentId(selected);
this.removeCss(parentId, "webix_sidebar_selected");
}
return true;
}
return false;
});
this.attachEvent("onAfterSelect", function(id){
var parentId = this.getParentId(id);
this.addCss(parentId, "webix_sidebar_selected");
var title = this.getPopupTitle();
title.callEvent("onMasterSelect",[id]);
});
this.attachEvent("onMouseMove", function(id, ev, node){
this._showPopup(id, node);
});
if(this.config.collapsed)
this.collapse();
},
_showPopup: function(id, node){
if(this.config.collapsed){
var popup = this.getPopup();
if(popup){
var title = this.getPopupTitle();
if(title){
this._updateTitle(id);
}
var list = this.getPopupList();
if(list){
this._updateList(id);
}
var x = (this.config.position == "left"?this.config.collapsedWidth:-popup.config.width);
popup.show(node, {x: x , y:-1});
}
}
},
_updateTitle: function(id){
var title = this.getPopupTitle();
title.masterId = id;
title.parse(this.getItem(id));
var selectedId = this.getSelectedId();
if(selectedId && this.getParentId(selectedId) == id){
webix.html.addCss(title.$view, "webix_sidebar_selected", true);
}
else{
webix.html.removeCss(title.$view, "webix_sidebar_selected");
}
if(selectedId == id){
webix.html.addCss(title.$view, "webix_selected", true);
}
else{
webix.html.removeCss(title.$view, "webix_selected");
}
},
_updateList: function(id){
var list = this.getPopupList();
list.masterId = id;
var selectedId = this.getSelectedId();
var data = [].concat(webix.copy(this.data.getBranch(id)));
list.unselect();
if(data.length){
list.show();
list.data.importData(data);
if(list.exists(selectedId))
list.select(selectedId);
}
else
list.hide();
},
_initContextMenu: function(){
var config = this.config,
popup;
if(config.popup){
popup = webix.$$(config.popup);
}
if(!popup){
var dirClassName = (config.position=="left"?"webix_sidebar_popup_left":"webix_sidebar_popup_right");
var popupConfig = {
view:"popup",
css: "webix_sidebar_popup "+dirClassName,
autofit: false,
width: this._fullWidth - this.config.collapsedWidth,
borderless: true,
padding:0,
body:{
rows:[
{
view: "template", borderless: true, css: "webix_sidebar_popup_title",
template: "#value#", height: this.config.titleHeight+2,
on:{
onMasterSelect: function(id){
var master = this.getTopParentView().master;
if( master && master.getParentId(id) == this.masterId){
webix.html.addCss(this.$view, "webix_sidebar_selected", true);
}
if(master.config.collapsed && master.getItem(id).$level ==1){
webix.html.addCss(this.$view, "webix_selected", true);
}
}
},
onClick:{
webix_template: function(){
var id = this.masterId;
var master = this.getTopParentView().master;
if(!master.getItem(id).$count)
master.select(id);
}
}
},
{ view: "list", select: true, borderless: true, css: "webix_sidebar_popup_list", autoheight: true,
on:{
onAfterSelect: function(id){
this.getTopParentView().master.select(id);
}
}
}
]
}
};
webix.extend(popupConfig, config.popup||{}, true);
popup = webix.ui(popupConfig);
popup.master = this;
}
popup.attachEvent("onBeforeShow",function(){
return config.collapsed;
});
var master = this;
var h = webix.event(document.body,"mousemove", function(e){
var trg = e.target || e.srcElement;
if(!popup.config.hidden && !popup.$view.contains(trg) && !master.$view.firstChild.contains(trg)){
popup.hide();
}
});
this.attachEvent("onDestruct", function(){
if(webix.removeEvent)
webix.removeEvent(h);
if(popup)
popup.destructor();
});
config.popupId = popup.config.id;
},
getPopup: function(){
return webix.$$(this.config.popupId);
},
getPopupTitle: function(){
var popup = this.getPopup();
return popup.getBody().getChildViews()[0];
},
getPopupList: function(){
var popup = this.getPopup();
return popup.getBody().getChildViews()[1];
},
position_setter:function(value){
var newPos = value;
var oldPos = value=="left"?"right":"left";
webix.html.removeCss(this.$view, "webix_sidebar_"+oldPos);
webix.html.addCss(this.$view, "webix_sidebar_"+newPos, true);
var popup = this.getPopup();
if(popup){
var popupEl = popup.$view;
webix.html.removeCss(popupEl, "webix_sidebar_popup_"+oldPos);
webix.html.addCss(popupEl, "webix_sidebar_popup_"+newPos, true);
}
return value;
},
collapse: function(){
this.define("collapsed", true);
},
expand: function(){
this.define("collapsed", false);
},
toggle: function(){
var collapsed = !this.config.collapsed;
this.define("collapsed", collapsed);
},
collapsed_setter: function(value){
var width;
if(!value){
width = this._fullWidth;
}
else{
width = this.config.collapsedWidth;
this.closeAll();
}
if(!value){
this.type.collapsed = false;
webix.html.addCss(this.$view, "webix_sidebar_expanded", true);
}
else{
this.type.collapsed = true;
webix.html.removeCss(this.$view, "webix_sidebar_expanded");
}
this.define("width",width);
this.resize();
return value;
}
}, webix.ui.tree);
webix.type(webix.ui.tree, {
name:"sideBar",
height: "auto",
css: "webix_sidebar",
template: function(obj, common){
if(common.collapsed)
return common.icon(obj, common);
return common.arrow(obj, common)+common.icon(obj, common) +"<span>"+obj.value+"</span>";
},
arrow: function(obj, common){
var html = "";
var open = "";
for (var i=1; i<=obj.$level; i++){
if (i==obj.$level && obj.$count){
var className = "webix_sidebar_dir_icon webix_icon fa-angle-"+(obj.open?"down":"left");
html+="<span class='"+className+"'></span>";
}
}
return html;
},
icon:function(obj, common){
if(obj.icon)
return "<span class='webix_icon webix_sidebar_icon fa-"+obj.icon+"'></span>";
return "";
}
});

View File

@ -0,0 +1,129 @@
var toolbar_invoices = [
{view: "button", id: "cmd_new_invoice", label: "Nueva", type: "iconButton",
autowidth: true, icon: "plus"},
{view: "button", id: "cmd_edit_invoice", label: "Editar", type: "iconButton",
autowidth: true, icon: "pencil"},
{view: "button", id: "cmd_delete_invoice", label: "Eliminar", type: "iconButton",
autowidth: true, icon: "minus"},
];
var grid_invoices_cols = [
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data", sort:"string"},
{id: "folio", header: ["Folio", {content: "numberFilter"}], adjust: "data", sort:"int", css: "cell_right"},
{id: "uuid", header: ["UUID", {content: "textFilter"}], adjust: "data", sort:"string"},
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort:"string"},
{id: "tipo", header: ["Tipo", {content: "selectFilter"}], adjust: "data", sort:"string"},
{id: "estatus", header: ["Estatus", {content: "selectFilter"}], adjust: "data", sort:"string"},
{id: "total", header: ["Total M.N.", {content: "numberFilter"}], width: 150, sort:"int", format: webix.i18n.priceFormat, css: "cell_right"},
{id: "partner", header: ["Razón Social", {content: "selectFilter"}], fillspace:true, sort:"string"},
];
var grid_invoices = {
view: "datatable",
id: "grid_invoices",
select: "row",
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_invoices_cols,
};
var grid_details_cols = [
{id:"id", header:"ID", hidden: true},
{id:"key", header:{text: 'Clave', css: 'center'}, width: 100},
{id:"description", header:{text: 'Descripción', css: 'center'}, fillspace: true},
{id:"unidad", header:{text: 'Unidad', css: 'center'}, width: 100},
{id:"cant", header:{text: 'Cantidad', css: 'center'}, width: 100, format: webix.i18n.priceFormat, css:'right'},
{id:"price", header:{text: 'Valor Unitario', css: 'center'}, width: 100, format: webix.i18n.priceFormat, css:'right'},
{id:"importe", header:{text: 'Importe', css: 'center'}, width: 150, format: webix.i18n.priceFormat, css:'right'},
];
var grid_details = {
view: "datatable",
id: "grid_details",
select: "row",
adjust: true,
autoheight: true,
columns: grid_details_cols,
data: [
{id:1, key:'001', description:'Uno', unidad:'Pieza', cant:1, price:100, importe:100},
{id:2, key:'002', description:'Dos', unidad:'Pieza', cant:2, price:200, importe:400},
]
};
var controls_generate = [
{view: "text", id: "search_partner", name: "search_partner", label: "Buscar Cliente", required: true, labelPosition: "top"},
{view: "text", id: "search_product", name: "search_product", label: "Buscar Producto", required: true, labelPosition: "top"},
{view:"label", label:'Detalle', height:30, align:'left'},
grid_details,
]
var controls_invoices = [
{
view: "tabview",
tabbar: {options: ["Generar"]}, animate: true,
cells: [
{id: "Generar", rows: controls_generate},
]
},
{rows: [
{ template:"", type: "section" },
{ margin: 10, cols: [{},
{view: "button", id: "cmd_save_invoice", label: "Guardar" , type: "form", autowidth: true, align:"center"},
{view: "button", id: "cmd_send_invoice", label: "Timbrar" , type: "form", autowidth: true, align:"center"},
{view: "button", id: "cmd_cancel_invoice", label: "Cancelar" , type: "danger", autowidth: true, align:"center"},
{}]
},
]}
];
var form_invoice = {
type: "space",
cols: [{
view: "form",
id: "form_invoice",
//~ width: 800,
complexData: true,
elements: controls_invoices,
//~ rules: {
//~ description: function(value){ return value.trim() != ""; },
//~ unidad: function(value){ return value.trim() != ""; },
//~ price: function(value){ return value.trim() != ""; },
//~ }
}]
};
var multi_invoices = {
id: "multi_invoices",
animate: true,
cells:[
{id: "invoices_home", rows:[
{view:"toolbar", elements: toolbar_invoices},
grid_invoices,
]},
{id: "invoices_new", rows:[form_invoice, {}]}
]
};
var app_invoices = {
id: "app_invoices",
rows:[
{view: "template", id: "th_invoices", type: "header", template:"Administración de Facturas" },
multi_invoices
]
};

View File

@ -0,0 +1,38 @@
var msg_user = 'El usuario es requerido'
var msg_pass = 'La contraseña es requerida'
var form_controls = [
{view: 'text', label: 'Usuario', name: 'user', labelPosition: 'top',
required: true, invalidMessage: msg_user},
{view: 'text', label: 'Contraseña', name: 'pass', type: 'password',
required: true, labelPosition: 'top', invalidMessage: msg_pass},
{margin: 10, cols:[{}, {view: 'button', value: 'Iniciar Sesión',
click: 'validate_login', hotkey: 'enter'}, {}]}
]
var msg_header = 'Bienvenido a Empresa Libre'
var ui_login = {
rows: [
{maxHeight: 50},
{view: 'template', template: msg_header, maxHeight: 50, css: 'login_header'},
{maxHeight: 50},
{cols: [{}, {type: 'space', padding: 5,
rows: [
{view: 'template', template: 'Acceso al sistema', type: 'header'},
{
container: 'form_login',
view: 'form',
id: 'form_login',
width: 400,
elements: form_controls,
rules:{
user:function(value){ return value.trim() != '';},
pass:function(value){ return value.trim() != '';},
}
},
]}, {}, ]
},
]
}

View File

@ -0,0 +1,76 @@
var menu_data = [
{id: "app_home", icon: "dashboard", value: "Inicio"},
{id: "app_partners", icon: "users", value: "Clientes" },
{id: "app_products", icon: "server", value:"Productos" },
{id: "app_invoices", icon: "cart-plus", value:"Facturas"},
];
var sidebar = {
view: "sidebar",
data: menu_data,
ready:function(){
this.select("app_home");
this.open(this.getParentId("app_home"));
},
on:{
onAfterSelect: function(id){
$$("multi").setValue(id)
}
}
};
var multi_main = {
id: "multi",
animate: true,
cells:[
{
id: "app_home",
view: "template",
template: "HOME"
},
app_partners,
app_products,
app_invoices,
]
};
var menu_user = {
view: 'menu',
id: 'menu_user',
width: 150,
autowidth: true,
data:[
{id:'0', value:'User...', submenu:[{id:1, value:'Cerrar Sesión'}]},
],
type:{
subsign:true,
},
};
var ui_main = {
rows: [
{view: "toolbar", padding:3, elements: [
{view: "button", type: "icon", icon: "bars",
width: 37, align: "left", css: "app_button", click: function(){
$$("$sidebar1").toggle()
}
},
{view: "label", label: "CFDI - Boveda Fiscal"},
{},
menu_user,
{view: "button", type: "icon", width: 45, css: "app_button", icon: "bell-o", badge:1}
]
},
{
cols:[
sidebar,
multi_main,
]
}
]
};

View File

@ -0,0 +1,131 @@
var toolbar_partners = [
{view: "button", id: "cmd_new_partner", label: "Nuevo", type: "iconButton",
autowidth: true, icon: "user-plus"},
{view: "button", id: "cmd_edit_partner", label: "Editar", type: "iconButton",
autowidth: true, icon: "user"},
{view: "button", id: "cmd_delete_partner", label: "Eliminar", type: "iconButton",
autowidth: true, icon: "user-times"},
];
webix.ui.datafilter.rowCount = webix.extend({
refresh:function(master, node, value){
node.firstChild.innerHTML = master.count();
}
}, webix.ui.datafilter.summColumn)
var grid_partners_cols = [
{ id:'index', header:'#', adjust:'data', css:'right', footer:{content:'rowCount'}},
{ id:"id", header:"Clave", width: 75, sort: "int", css: 'right', footer:'Clientes'},
{ id:"cia", header: ["CIA", {content: "textFilter"}], width: 100, sort:"string" },
{ id:"rfc", header: ["RFC", {content: "textFilter"}], width: 150, sort:"string" },
{ id:"name", header: ["Razón Social", {content: "textFilter"}], fillspace:true, sort:"string"},
];
var grid_partners = {
view: "datatable",
id: "grid_partners",
select: "row",
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_partners_cols,
on:{
'data->onStoreUpdated':function(){
this.data.each(function(obj, i){
obj.index = i+1;
})
}
},
//~ onClick:{
//~ cmd_edit_parter: cmd_edit_partner_click,
//~ },
}
var controls_fiscales = [
{cols:[{view:"text", id: "cia", name: "cia", label: "CIA", width: 300, required: true, invalidMessage: "CIA inválido", attributes: {maxlength: 11}},{}]},
{cols:[{view:"text", id: "rfc", name: "rfc", label: "RFC", width: 300, required: true, invalidMessage: "RFC inválido", attributes: {maxlength: 13}},{}]},
{view:"text", id: "name", name: "name", label: "Razón Social", required: true, invalidMessage: "La Razón Social es requerida"},
{view:"text", id: "street", name: "street", label: "Calle"},
{cols:[{view:"text", id: "num_ext", name: "num_ext", width: 300, label: "No Exterior"},{}]},
{cols:[{view:"text", id: "num_int", name: "num_int", width: 300, label: "No Interior"},{}]},
{cols:[{view:"search", id: "postal_code", name: "postal_code", width: 300, label: "C.P.", attributes: {maxlength: 5}},{}]},
{view:"text", id: "colonia", name: "colonia", label: "Colonia"},
{view:"text", id: "municipio", name: "municipio", label: "Municipio"},
{view:"search", id: "state", name: "state", label: "Estado"},
{view:"text", id:"country", name:"country", label: "País", value: "México"},
{template:"Contacto", type:"section"},
{view:"text", id: "contact", name: "contact", label: "Nombre"},
{view:"text", id: "email", name: "email", label: "Correo"},
{view:"text", id: "phone", name: "phone", label: "Teléfono"},
]
var controls_partner = [
{
view: "tabview",
tabbar: {options: ["Datos Fiscales"]}, animate: true,
cells: [
{id: "Datos Fiscales", rows: controls_fiscales},
]
},
{rows: [
{ template:"", type: "section" },
{ margin: 10, cols: [{},
{view: "button", id: "cmd_save_partner", label: "Guardar" , type: "form", autowidth: true, align:"center"},
{view: "button", id: "cmd_cancel_partner", label: "Cancelar" , type: "danger", autowidth: true, align:"center"},
{}]
},
]}
];
var form_partner = {
type: "space",
cols: [{
view: "form",
id: "form_partner",
//~ width: 600,
complexData: true,
elements: controls_partner,
elementsConfig: {
labelWidth: 130,
labelAlign: "right"
},
autoheight: true,
rules: {
cia: function(value){ return value.trim() != ""; },
name: function(value){ return value.trim() != ""; },
rfc: validate_rfc,
}
}]
};
var multi_partners = {
id: "multi_partners",
animate: true,
cells:[
{id: "partners_home", rows:[
{view:"toolbar", elements: toolbar_partners},
grid_partners,
]},
{id: "partners_new", rows:[form_partner, {}]}
]
};
var app_partners = {
id: "app_partners",
rows:[
{view: "template", id: "th_partner", type: "header", template:"Administración de Clientes" },
multi_partners
]
};

View File

@ -0,0 +1,101 @@
var toolbar_products = [
{view: "button", id: "cmd_new_product", label: "Nuevo", type: "iconButton",
autowidth: true, icon: "plus"},
{view: "button", id: "cmd_edit_product", label: "Editar", type: "iconButton",
autowidth: true, icon: "pencil"},
{view: "button", id: "cmd_delete_product", label: "Eliminar", type: "iconButton",
autowidth: true, icon: "minus"},
];
var grid_products_cols = [
{ id:"id", header:"ID", width: 75},
{ id:"key", header: ["Clave", {content: "textFilter"}], width: 100, sort:"string" },
{ id:"description", header: ["Descripción", {content: "textFilter"}], fillspace:true, sort:"string" },
{ id:"unidad", header: ["Unidad", {content: "selectFilter"}], width: 150, sort:"string" },
{ id:"price", header: ["Precio", {content: "numberFilter"}], width: 150, sort:"int", format: webix.i18n.priceFormat, css: "cell_right" },
];
var grid_products = {
view: "datatable",
id: "grid_products",
select: "row",
adjust: true,
footer: true,
resizeColumn: true,
headermenu: true,
columns: grid_products_cols,
//~ onClick:{
//~ cmd_edit_parter: cmd_edit_partner_click,
//~ },
};
var controls_generals = [
{view: "textarea", id: "description", name: "description", height: 200, label: "Descripción", required: true, labelPosition: "top", invalidMessage: "La Descripción es requerida" },
{cols: [{view: "text", id: "unidad", name: "unidad", label: "Unidad", width: 300, labelWidth: 130, labelAlign: "right", required: true, invalidMessage: "La Unidad es requerida" },{}]},
{cols: [{view: "search", type: "text", id: "price", name: "price", label: "Valor Unitario", width: 300, labelWidth: 130, labelAlign: "right", required: true, icon:"calculator", invalidMessage: "Captura un valor númerico", inputAlign:"right" },{}]},
]
var controls_products = [
{
view: "tabview",
tabbar: { options: ["Generales"]}, animate: true,
cells: [
{id: "Generales", rows: controls_generals},
]
},
{rows: [
{ template:"", type: "section" },
{ margin: 10, cols: [{},
{view: "button", id: "cmd_save_product", label: "Guardar" , type: "form", autowidth: true, align:"center"},
{view: "button", id: "cmd_cancel_product", label: "Cancelar" , type: "danger", autowidth: true, align:"center"},
{}]
},
]}
];
var form_product = {
type: "space",
cols: [{
view: "form",
id: "form_product",
//~ width: 600,
complexData: true,
elements: controls_products,
rules: {
description: function(value){ return value.trim() != ""; },
unidad: function(value){ return value.trim() != ""; },
price: function(value){ return value.trim() != ""; },
}
}]
};
var multi_products = {
id: "multi_products",
animate: true,
cells:[
{id: "products_home", rows:[
{view:"toolbar", elements: toolbar_products},
grid_products,
]},
{id: "product_new", rows:[form_product, {}]}
]
};
var app_products = {
id: "app_products",
rows:[
{view: "template", id: "th_products", type: "header", template:"Administración de Productos" },
multi_products
]
};

1985
source/static/js/webix.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Boveda Fiscal</title>
<meta charset="utf-8" />
<link rel="shortcut icon" href="/static/img/favicon.png">
<link rel="stylesheet" href="/static/css/webix.css" type="text/css">
<link rel="stylesheet" href="/static/css/app.css" type="text/css">
<script src="/static/js/webix_debug.js" type="text/javascript" ></script>
<script src="/static/js/es.js" type="text/javascript" ></script>
<%block name="media"/>
</head>
<body>
<script type="text/javascript" charset="utf-8">
webix.debug = true;
</script>
<%block name="content"/>
</body>
</html>

View File

@ -0,0 +1,39 @@
<%inherit file="base.html"/>
<%block name="media">
<script src="/static/js/ui/login.js" type="text/javascript" ></script>
</%block>
<%block name="content">
<div id="form_login"></div>
<script type="text/javascript" charset="utf-8">
function validate_login(){
var form = this.getFormView();
if (!form.validate()) {
webix.message({ type:"error", text:"Valores inválidos" });
return
}
var values = form.getValues();
webix.ajax().post("/", values, function(text, data, xhr) {
var values = data.json();
if (values.login) {
window.location = "/main";
} else {
webix.message({ type:"error", text: values.msg });
}
});
};
webix.ready(function(){
webix.ui(ui_login);
});
</script>
</%block>

View File

@ -0,0 +1,16 @@
<%inherit file="base.html"/>
<%block name="media">
</%block>
<%block name="content">
<script type="text/javascript" charset="utf-8">
window.location = "/";
};
</script>
</%block>

View File

@ -0,0 +1,36 @@
<%inherit file="base.html"/>
<%block name="media">
<link rel="stylesheet" href="/static/css/sidebar.css" type="text/css">
<script src="/static/js/sidebar.js" type="text/javascript" ></script>
<script src="/static/js/controller/util.js" type="text/javascript" ></script>
<script src="/static/js/ui/partners.js" type="text/javascript" ></script>
<script src="/static/js/ui/products.js" type="text/javascript" ></script>
<script src="/static/js/ui/invoices.js" type="text/javascript" ></script>
<script src="/static/js/ui/main.js" type="text/javascript" ></script>
<script src="/static/js/controller/partners.js" type="text/javascript" ></script>
<script src="/static/js/controller/products.js" type="text/javascript" ></script>
<script src="/static/js/controller/invoices.js" type="text/javascript" ></script>
<script src="/static/js/controller/main.js" type="text/javascript" ></script>
</%block>
<%block name="content">
<input type=hidden id="username" value="${username}"/>
<script type="text/javascript" charset="utf-8">
webix.ready(function(){
webix.CustomScroll.init()
webix.ui(ui_main);
controllers.init();
var user = document.getElementById("username").value;
$$('menu_user').getMenu(0).updateItem(0, {value:user});
});
</script>
</%block>