Initial commit

This commit is contained in:
resteve 2013-05-28 12:00:04 +02:00
commit 809fb268cc
41 changed files with 13421 additions and 0 deletions

14
COPYRIGHT Executable file
View File

@ -0,0 +1,14 @@
Copyright (C) 2013 Zikzakmedia SL.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

25
INSTALL Executable file
View File

@ -0,0 +1,25 @@
Installing Flask OpenERP Stock Cart
===================================
Prerequisites
-------------
* Flask (https://pypi.python.org/pypi/Flask)
* Flask-Babel (https://pypi.python.org/pypi/Flask-Babel)
* Flask-WTF (https://pypi.python.org/pypi/Flask-WTF)
* ERPPeek (https://pypi.python.org/pypi/ERPpeek)
Configuration
-------------
config.ini
* APP NAME
config.cfg
* Debug
* Template
* Language
* Payments

53
README Normal file
View File

@ -0,0 +1,53 @@
Flask OpenERP Stock Cart
========================
Flask OpenERP Stock Cart is a Flask App to management stock pickings in warehouse.
Installing
----------
See INSTALL
OpenERP modules
---------------
* Stock
* Stock Cart
* Product Trademark
* Nan Stock Scanner
OpenERP Configuration
---------------------
Edit config.ini and add OpenERP connection:
OPENERP_SERVER = 'http://localhost:8069'
OPENERP_DATABASE = 'database'
Username and password is from login form.
Check username have permissions to read and write in models:
* stock.picking
* stock.cart
Support
-------
For more information or if you encounter any problems with this module,
please contact the programmers at
Zikzakmedia
--------------
website: http://www.zikzakmedia.com/
email: zikzak@zikzakmedia.com
License
-------
See LICENSE
Copyright
---------
See COPYRIGHT

271
app.py Normal file
View File

@ -0,0 +1,271 @@
#This file is part openerp-stock-cart app for Flask.
#The COPYRIGHT file at the top level of this repository contains
#the full copyright notices and license terms.
import os
import ConfigParser
import erppeek
import bz2
import socket
from functools import wraps
from flask import Flask, render_template, request, jsonify, abort, session, redirect, url_for, flash
from flask.ext.babel import Babel, gettext as _
from apphelp import get_description
from form import *
from defaultfilters import *
def get_config():
'''Get values from cfg file'''
conf_file = '%s/config.ini' % os.path.dirname(os.path.realpath(__file__))
config = ConfigParser.ConfigParser()
config.read(conf_file)
results = {}
for section in config.sections():
results[section] = {}
for option in config.options(section):
results[section][option] = config.get(section, option)
return results
def create_app(config=None):
'''Create Flask APP'''
cfg = get_config()
app_name = cfg['flask']['app_name']
app = Flask(app_name)
app.config.from_pyfile(config)
return app
def get_template(tpl):
'''Get template'''
return "%s/%s" % (app.config.get('TEMPLATE'), tpl)
def parse_setup(filename):
globalsdict = {} # put predefined things here
localsdict = {} # will be populated by executed script
execfile(filename, globalsdict, localsdict)
return localsdict
def get_lang():
return app.config.get('LANGUAGE')
def erp_connect():
'''OpenERP Connection'''
server = app.config.get('OPENERP_SERVER')
database = app.config.get('OPENERP_DATABASE')
username = session['username']
password = bz2.decompress(session['password'])
try:
Client = erppeek.Client(server, db=database, user=username, password=password)
except socket.error:
flash(_("Can't connect to ERP server. Check network-ports or ERP server was running."))
abort(500)
except:
abort(500)
return Client
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
logged = session.get('logged_in', None)
if not logged:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
conf_file = '%s/config.cfg' % os.path.dirname(os.path.realpath(__file__))
app = create_app(conf_file)
app.config['BABEL_DEFAULT_LOCALE'] = get_lang()
app.root_path = os.path.dirname(os.path.abspath(__file__))
babel = Babel(app)
# OpenERP models - access
MODELS_ACCESS = ({
'stock.picking': 'write',
'stock.cart': 'read',
})
@app.errorhandler(404)
def page_not_found(e):
return render_template(get_template('404.html')), 404
@app.errorhandler(500)
def server_error(e):
return render_template(get_template('500.html')), 500
@app.route('/')
@login_required
def index():
'''Dashboard'''
cart = session.get('cart', None)
if not cart:
return redirect(url_for('cart'))
return render_template(get_template('index.html'))
@app.route("/login", methods=["GET", "POST"])
def login():
'''Login'''
form = LoginForm()
if form.validate_on_submit():
username = request.form.get('username')
password = bz2.compress(request.form.get('password'))
session['username'] = username
session['password'] = password
Client = erp_connect()
login = Client.login(username, bz2.decompress(password), app.config.get('OPENERP_DATABASE'))
if login:
access = True
for key, value in MODELS_ACCESS.iteritems():
if not Client.access(key, mode=value):
access = False
flash(_('Error: Not access in %(key)s - %(value)s' % { 'key': key, 'value': value} ))
if access:
session['logged_in'] = True
flash(_('You were logged in.'))
return redirect(url_for('index'))
else:
flash(_('Error: Invalid username or password'))
return render_template(get_template('login.html'), form=form)
@app.route('/logout')
def logout():
'''Logout App'''
Client = erp_connect()
# Remove all stock.cart by user
StockCart = Client.model('stock.cart')
user_id = Client.search('res.users',[
('login', '=', session.get('username')),
])[0]
carts = Client.search('stock.cart',[
('user_id', '=', user_id),
])
for cart in carts:
c = StockCart.get(cart)
c.write({'user_id': None})
# Remove all sessions
session.pop('logged_in', None)
session.pop('username', None)
session.pop('password', None)
session.pop('cart', None)
session.pop('cart_name', None)
flash(_('You were logged out.'))
return redirect(url_for('login'))
@app.route('/cart', methods=["GET"])
@login_required
def cart():
'''Select a stock cart by user'''
Client = erp_connect()
StockCart = Client.model('stock.cart')
'''Drop currenty cart by user'''
if session.get('cart'):
cart_old = StockCart.get(int(session.get('cart')))
cart_old.write({'user_id': None})
session.pop('cart', None)
session.pop('cart_name', None)
'''Get cart id and add who working in stock.cart'''
if request.method == 'GET':
cart = request.args.get('cart', None)
order = request.args.get('order', None)
if cart:
try:
# Get Cart object from request.get
id = int(cart)
cart = StockCart.get(id)
if not cart.user_id and cart.active:
#get user id
user_id = Client.search('res.users',[
('login', '=', session.get('username')),
])[0]
# Add new session values and log in stock.cart
session['cart'] = cart.id
session['cart_name'] = cart.name
session['cart_rows'] = cart.rows
session['cart_columns'] = cart.columns
cart.write({'user_id': user_id})
return redirect(url_for('index'))
else:
if cart.user_id:
flash(_(u'Cart %(name)s is working by user %(user)s.', name=cart.name, user=cart.user_id))
else:
flash(_(u'Not find some cart by %(id)s.', id=cart))
except:
flash(_('Error: Cart ID not valid or empty'))
if order:
flash(_(u'Order products by: %(order)s.', order=order))
session['order'] = order
carts = StockCart.browse([('active', '=', True)])
return render_template(get_template('cart.html'), carts=carts)
@app.route('/picking')
@login_required
def picking():
'''Get all pickings and get info to make pickings'''
cart = session.get('cart', None)
order = session.get('order', None)
if not cart:
return redirect(url_for('cart'))
Client = erp_connect()
products, picking_grid = Client.execute('stock.picking', 'get_products_to_cart', cart, order)
return render_template(get_template('picking.html'), products=products, grid=picking_grid)
@app.route('/basket', methods=['PUT', 'POST'])
def basket():
'''
Process values from form basket
name: picking-product ID's
value: qty to add in basket
'''
cart = session.get('cart', None)
Client = erp_connect()
values = []
result = True
for data in request.json:
try:
move = int(data['name'])
qty = int(data['value'])
except:
result = False
values.append({
'move': move,
'qty': qty,
})
if result:
Client.execute('stock.picking', 'set_products_to_cart', cart, values)
return jsonify(result=result)
@app.route('/help')
def help():
'''Help - Documentation'''
lang = get_lang()
description = get_description(lang)
return render_template(get_template('help.html'), content=description)
if __name__ == "__main__":
app.run()

64
apphelp.py Normal file
View File

@ -0,0 +1,64 @@
#This file is part openerp-stock-cart app for Flask.
#The COPYRIGHT file at the top level of this repository contains
#the full copyright notices and license terms.
import os
import docutils.core
def get_description(lang):
'''Get Description module from doc rst'''
description = ''
doc_path = 'doc/%s/index.rst' % (lang)
if os.path.exists(doc_path):
return read_rst(doc_path)
doc_path = 'doc/index.rst'
if os.path.exists(doc_path):
return read_rst(doc_path)
return description
def read_rst(doc_path):
f = open(doc_path, "r")
description = f.read()
def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
destination_path=None, reader=None, reader_name='standalone',
parser=None, parser_name='restructuredtext', writer=None,
writer_name='html', settings=None, settings_spec=None,
settings_overrides=None, config_section=None,
enable_exit_status=None):
"""
Set up & run a `Publisher`, and return a dictionary of document parts.
Dictionary keys are the names of parts, and values are Unicode strings;
encoding is up to the client. For programmatic use with string I/O.
For encoded string input, be sure to set the 'input_encoding' setting to
the desired encoding. Set it to 'unicode' for unencoded Unicode string
input. Here's how::
publish_parts(..., settings_overrides={'input_encoding': 'unicode'})
Parameters: see `publish_programmatically`.
"""
output, pub = docutils.core.publish_programmatically(
source=source, source_path=source_path, source_class=source_class,
destination_class=docutils.io.StringOutput,
destination=None, destination_path=destination_path,
reader=reader, reader_name=reader_name,
parser=parser, parser_name=parser_name,
writer=writer, writer_name=writer_name,
settings=settings, settings_spec=settings_spec,
settings_overrides=settings_overrides,
config_section=config_section,
enable_exit_status=enable_exit_status)
return pub.writer.parts['fragment'], pub.document.reporter.max_level, pub.settings.record_dependencies
output, error_level, deps = rst2html(
description, settings_overrides={
'initial_header_level': 2,
'record_dependencies': True,
'stylesheet_path': None,
'link_stylesheet': True,
'syntax_highlight': 'short',
})
return output

3
babel.cfg Normal file
View File

@ -0,0 +1,3 @@
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

13
config.cfg.template Normal file
View File

@ -0,0 +1,13 @@
DEBUG = True
DEBUG_LOG = 'logs/debug.log'
ERROR_LOG = 'logs/error.log'
ADMINS = ('user@domain.com',)
TITLE = 'OpenERP Stock Cart'
TEMPLATE = 'default'
LANGUAGE = 'es'
AUTHOR = 'Zikzakmedia'
SECRET_KEY = 'C5G94WB6BVRPHTO85RGI2Y6TM6HYY0P'
OPENERP_SERVER = 'http://localhost:8069'
OPENERP_DATABASE = 'databasename'

2
config.ini.template Normal file
View File

@ -0,0 +1,2 @@
[flask]
APP_NAME = 'openerpstockcart'

35
defaultfilters.py Normal file
View File

@ -0,0 +1,35 @@
#This file is part openerp-stock-cart app for Flask.
#The COPYRIGHT file at the top level of this repository contains
#the full copyright notices and license terms.
import jinja2
def floatwithoutdecimal(text):
"""
Convert a float to integer.
* num1 = 34.23234
* num2 = 34.00000
* num3 = 34.26000
* {{ num1|floatformat }} displays "34"
* {{ num2|floatformat }} displays "34"
* {{ num3|floatformat }} displays "34"
"""
try:
float(text)
except:
return ''
return '{0:g}'.format(float(text))
jinja2.filters.FILTERS['floatwithoutdecimal'] = floatwithoutdecimal
def pickingname(pickings, arg):
"""
Get picking name from dict.
* pickings = {'1': 'P1230', '2': 'P1231'}
"""
if not arg:
return None
return pickings[str(arg)]
jinja2.filters.FILTERS['pickingname'] = pickingname

26
doc/es/index.rst Normal file
View File

@ -0,0 +1,26 @@
------------------
OpenERP Stock Cart
------------------
Esta herramienta le permitirá gestionar los productos y albaranes de vuestro almacén.
* Seleccione que carrito va a usar en sus preferencias. Sólo podrá usar un carrito
a la vez y sólo podrá seleccionar los carritos que estén disponibles.
* Si un usuario quiere abandonar o cambiar de carrito, acceda a la sección de **Carros**
para liberar el carro o cambiar.
Cajas del carro
---------------
Un carro dispone de varias cajas. Cada caja del carro equivale a un albarán. En
cada caja deberá poner los productos que tiene cada albarán.
Proceso del carro
-----------------
Cuando inicie el proceso de recogida de productos, se le mostrará un listado de
productos y el número de productos a recoger. Por cada producto, sabrá cuantas unidades
debe dejar en cada caja del carro. Recuerde que cada caja del carro equivale a un albarán.
Una vez de vuelta al muelle de salida, deberá revisar que en cada caja del carro haya
los productos correspondientes y ya podrá ejecutar el empaquetado y el etiquetaje.

6
doc/index.rst Normal file
View File

@ -0,0 +1,6 @@
------------------
OpenERP Stock Cart
------------------
Cart management warehouse. Select products by cart and make pickings when finish
all process from products.

19
form.py Normal file
View File

@ -0,0 +1,19 @@
#This file is part openerp-sale-payment app for Flask.
#The COPYRIGHT file at the top level of this repository contains
#the full copyright notices and license terms.
from flask.ext.wtf import Form
from wtforms import TextField, PasswordField, validators
class LoginForm(Form):
username = TextField('Username', [validators.Required()])
password = PasswordField('Password', [validators.Required()])
def __init__(self, *args, **kwargs):
Form.__init__(self, *args, **kwargs)
def validate(self):
rv = Form.validate(self)
if not rv:
return False
return True

242
messages.pot Normal file
View File

@ -0,0 +1,242 @@
# Translations template for PROJECT.
# Copyright (C) 2013 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-05-28 11:34+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
#: app.py:64
msgid ""
"Can't connect to ERP server. Check network-ports or ERP server was "
"running."
msgstr ""
#: app.py:128
#, python-format
msgid "Error: Not access in %(key)s - %(value)skey"
msgstr ""
#: app.py:131
msgid "You were logged in."
msgstr ""
#: app.py:134
msgid "Error: Invalid username or password"
msgstr ""
#: app.py:163
msgid "You were logged out."
msgstr ""
#: app.py:207
#, python-format
msgid "Cart %(name)s is working by user %(user)s."
msgstr ""
#: app.py:209
#, python-format
msgid "Not find some cart by %(id)s."
msgstr ""
#: app.py:211
msgid "Error: Cart ID not valid or empty"
msgstr ""
#: app.py:214
#, python-format
msgid "Order products by: %(order)s."
msgstr ""
#: templates/default/cart.html:2 templates/default/index.html:8
#: templates/default/index.html:18 templates/default/nav.html:23
#: templates/default/picking.html:2
msgid "Select a cart"
msgstr ""
#: templates/default/cart.html:7
msgid "Order products by"
msgstr ""
#: templates/default/cart.html:11
msgid "Name"
msgstr ""
#: templates/default/cart.html:12
msgid "Baskets"
msgstr ""
#: templates/default/cart.html:13
msgid "User"
msgstr ""
#: templates/default/cart.html:23
msgid "Get this cart"
msgstr ""
#: templates/default/help.html:9
msgid "Close"
msgstr ""
#: templates/default/index.html:2
msgid "Dashboard"
msgstr ""
#: templates/default/index.html:8 templates/default/nav.html:23
msgid "Change cart"
msgstr ""
#: templates/default/index.html:9 templates/default/index.html:24
#: templates/default/nav.html:13
msgid "Process pickings"
msgstr ""
#: templates/default/index.html:10 templates/default/nav.html:24
msgid "Help"
msgstr ""
#: templates/default/index.html:19
msgid ""
"Go to your preferences a select a cart do you work. Remember when not "
"working in warehouse, do logout"
msgstr ""
#: templates/default/index.html:19 templates/default/index.html:25
msgid "Go"
msgstr ""
#: templates/default/index.html:25
msgid ""
"When process pickings, select products in your warehouse and put in you "
"backet cart."
msgstr ""
#: templates/default/index.html:30
msgid "Make picking"
msgstr ""
#: templates/default/index.html:31
msgid ""
"When finish selection products, return your picking area and make "
"delivery package."
msgstr ""
#: templates/default/login.html:2 templates/default/login.html:47
msgid "Login"
msgstr ""
#: templates/default/login.html:14
msgid "Add a user name"
msgstr ""
#: templates/default/login.html:15
msgid "Add a password"
msgstr ""
#: templates/default/login.html:34
msgid "Username"
msgstr ""
#: templates/default/login.html:40
msgid "Password"
msgstr ""
#: templates/default/nav.html:17
msgid "Welcome"
msgstr ""
#: templates/default/nav.html:19
msgid "Order by"
msgstr ""
#: templates/default/nav.html:20
msgid "Location"
msgstr ""
#: templates/default/nav.html:21
msgid "Manufacturer"
msgstr ""
#: templates/default/nav.html:25
msgid "Logout"
msgstr ""
#: templates/default/picking.html:11
msgid "Code"
msgstr ""
#: templates/default/picking.html:12
msgid "Product"
msgstr ""
#: templates/default/picking.html:13 templates/default/picking.html:52
msgid "Qty"
msgstr ""
#: templates/default/picking.html:14
msgid "Qty Available"
msgstr ""
#: templates/default/picking.html:15
msgid "Rack"
msgstr ""
#: templates/default/picking.html:16
msgid "Row"
msgstr ""
#: templates/default/picking.html:17
msgid "Case"
msgstr ""
#: templates/default/picking.html:21
msgid "There are not pickings to process"
msgstr ""
#: templates/default/picking.html:52
msgid "Qty added in this basket "
msgstr ""
#: templates/default/picking.html:62
msgid "Process"
msgstr ""
#: templates/default/picking.html:68
msgid "Grid Cart"
msgstr ""
#: templates/default/picking.html:81
msgid "Send quantity products successfully"
msgstr ""
#: templates/default/picking.html:84
msgid "Get more products/pickings to do"
msgstr ""
#: templates/default/picking.html:87
msgid "Done"
msgstr ""
#: templates/default/picking.html:95
msgid "Error!"
msgstr ""
#: templates/default/picking.html:98
msgid "Qty are integer values? Review your cart and resend"
msgstr ""
#: templates/default/picking.html:101
msgid "Check"
msgstr ""

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
Flask-Babel>=0.8
Flask-WTF>=0.8.3
erppeek>=1.4.5

214
static/default/css/bootstrap-modal.css vendored Normal file
View File

@ -0,0 +1,214 @@
/*!
* Bootstrap Modal
*
* Copyright Jordan Schroter
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.modal-open {
overflow: hidden;
}
/* add a scroll bar to stop page from jerking around */
.modal-open.page-overflow .page-container,
.modal-open.page-overflow .page-container .navbar-fixed-top,
.modal-open.page-overflow .page-container .navbar-fixed-bottom,
.modal-open.page-overflow .modal-scrollable {
overflow-y: scroll;
}
@media (max-width: 979px) {
.modal-open.page-overflow .page-container .navbar-fixed-top,
.modal-open.page-overflow .page-container .navbar-fixed-bottom {
overflow-y: visible;
}
}
.modal-scrollable {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: auto;
}
.modal {
outline: none;
position: absolute;
margin-top: 0;
top: 50%;
overflow: visible; /* allow content to popup out (i.e tooltips) */
}
.modal.fade {
top: -100%;
-webkit-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out;
-moz-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out;
-o-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out;
transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out;
}
.modal.fade.in {
top: 50%;
}
.modal-body {
max-height: none;
overflow: visible;
}
.modal.modal-absolute {
position: absolute;
z-index: 950;
}
.modal .loading-mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-radius: 6px;
}
.modal-backdrop.modal-absolute{
position: absolute;
z-index: 940;
}
.modal-backdrop,
.modal-backdrop.fade.in{
opacity: 0.7;
filter: alpha(opacity=70);
background: #fff;
}
.modal.container {
width: 940px;
margin-left: -470px;
}
/* Modal Overflow */
.modal-overflow.modal {
top: 1%;
}
.modal-overflow.modal.fade {
top: -100%;
}
.modal-overflow.modal.fade.in {
top: 1%;
}
.modal-overflow .modal-body {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
/* Responsive */
@media (min-width: 1200px) {
.modal.container {
width: 1170px;
margin-left: -585px;
}
}
@media (max-width: 979px) {
.modal,
.modal.container,
.modal.modal-overflow {
top: 1%;
right: 1%;
left: 1%;
bottom: auto;
width: auto !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
}
.modal.fade.in,
.modal.container.fade.in,
.modal.modal-overflow.fade.in {
top: 1%;
bottom: auto;
}
.modal-body,
.modal-overflow .modal-body {
position: static;
margin: 0;
height: auto !important;
max-height: none !important;
overflow: visible !important;
}
.modal-footer,
.modal-overflow .modal-footer {
position: static;
}
}
.loading-spinner {
position: absolute;
top: 50%;
left: 50%;
margin: -12px 0 0 -12px;
}
/*
Animate.css - http://daneden.me/animate
Licensed under the license (http://licence.visualidiot.com/)
Copyright (c) 2012 Dan Eden*/
.animated {
-webkit-animation-duration: 1s;
-moz-animation-duration: 1s;
-o-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
-o-animation-fill-mode: both;
animation-fill-mode: both;
}
@-webkit-keyframes shake {
0%, 100% {-webkit-transform: translateX(0);}
10%, 30%, 50%, 70%, 90% {-webkit-transform: translateX(-10px);}
20%, 40%, 60%, 80% {-webkit-transform: translateX(10px);}
}
@-moz-keyframes shake {
0%, 100% {-moz-transform: translateX(0);}
10%, 30%, 50%, 70%, 90% {-moz-transform: translateX(-10px);}
20%, 40%, 60%, 80% {-moz-transform: translateX(10px);}
}
@-o-keyframes shake {
0%, 100% {-o-transform: translateX(0);}
10%, 30%, 50%, 70%, 90% {-o-transform: translateX(-10px);}
20%, 40%, 60%, 80% {-o-transform: translateX(10px);}
}
@keyframes shake {
0%, 100% {transform: translateX(0);}
10%, 30%, 50%, 70%, 90% {transform: translateX(-10px);}
20%, 40%, 60%, 80% {transform: translateX(10px);}
}
.shake {
-webkit-animation-name: shake;
-moz-animation-name: shake;
-o-animation-name: shake;
animation-name: shake;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

6158
static/default/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

9
static/default/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,113 @@
body {padding-top: 0px; padding-bottom: 40px; background-color: #f5f5f5}
.error {color:#FF0000; font-size: 13px !important}
.stock-alert {color:#FF0000 !important}
#footer { text-align: center}
.footer-menu li { display: inline; list-style-type: none; padding-right: 20px;}
/* Login template */
.login-box {padding-top: 40px}
.login-form {max-width: 400px; padding: 19px 29px 29px; margin: 0 auto 20px; background-color: #fff; border: 1px solid #e5e5e5;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.login-form .login-form-heading,
.login-form .checkbox { margin-bottom: 10px}
.login-form input[type="text"],
.login-form label.error, label.error { color: red}
.login-form legend { font-size:16px; font-weight: bold !important}
/* Subnav Toolbar */
.subnav {
width: 100%;
height: 36px;
background-color: #eeeeee;
/* Old browsers */
background-repeat: repeat-x;
/* Repeat the gradient */
background-image: -moz-linear-gradient(top, #f5f5f5 0%, #eeeeee 100%);
/* FF3.6+ */
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f5f5f5), color-stop(100%,#eeeeee));
/* Chrome,Safari4+ */
background-image: -webkit-linear-gradient(top, #f5f5f5 0%,#eeeeee 100%);
/* Chrome 10+,Safari 5.1+ */
background-image: -ms-linear-gradient(top, #f5f5f5 0%,#eeeeee 100%);
/* IE10+ */
background-image: -o-linear-gradient(top, #f5f5f5 0%,#eeeeee 100%);
/* Opera 11.10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f5f5f5', endColorstr='#eeeeee',GradientType=0 );
/* IE6-9 */
background-image: linear-gradient(top, #f5f5f5 0%,#eeeeee 100%);
/* W3C */
border: 1px solid #e5e5e5;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.subnav .nav {
margin-bottom: 0;
}
.subnav .nav > li > a {
color: #333333;
margin: 0;
padding-top: 11px;
padding-bottom: 11px;
border-left: 1px solid #f5f5f5;
border-right: 1px solid #e5e5e5;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.subnav .nav > .active > a,
.subnav .nav > .active > a:hover {
padding-left: 13px;
color: #777;
background-color: #e9e9e9;
border-right-color: #ddd;
border-left: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.05);
-moz-box-shadow: inset 0 3px 5px rgba(0,0,0,.05);
box-shadow: inset 0 3px 5px rgba(0,0,0,.05);
}
.subnav .nav > .active > a .caret,
.subnav .nav > .active > a:hover .caret {
border-top-color: #777;
}
.subnav .nav > li:first-child > a,
.subnav .nav > li:first-child > a:hover {
border-left: 0;
padding-left: 12px;
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
.subnav .nav > li:last-child > a {
border-right: 0;
}
.subnav .dropdown-menu {
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px;
}
/* End Subnav */
/* Index */
.index-box { padding-top: 10px}
/* End Index */
/* Picking */
.basket { min-height:60px !important}
#send-process { text-align: right}
/* End picking */

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

374
static/default/js/bootstrap-modal.js vendored Normal file
View File

@ -0,0 +1,374 @@
/* ===========================================================
* bootstrap-modal.js v2.1
* ===========================================================
* Copyright 2012 Jordan Schroter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* MODAL CLASS DEFINITION
* ====================== */
var Modal = function (element, options) {
this.init(element, options);
};
Modal.prototype = {
constructor: Modal,
init: function (element, options) {
this.options = options;
this.$element = $(element)
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this));
this.options.remote && this.$element.find('.modal-body').load(this.options.remote);
var manager = typeof this.options.manager === 'function' ?
this.options.manager.call(this) : this.options.manager;
manager = manager.appendModal ?
manager : $(manager).modalmanager().data('modalmanager');
manager.appendModal(this);
},
toggle: function () {
return this[!this.isShown ? 'show' : 'hide']();
},
show: function () {
var e = $.Event('show');
if (this.isShown) return;
this.$element.trigger(e);
if (e.isDefaultPrevented()) return;
this.escape();
this.tab();
this.options.loading && this.loading();
},
hide: function (e) {
e && e.preventDefault();
e = $.Event('hide');
this.$element.trigger(e);
if (!this.isShown || e.isDefaultPrevented()) return (this.isShown = false);
this.isShown = false;
this.escape();
this.tab();
this.isLoading && this.loading();
$(document).off('focusin.modal');
this.$element
.removeClass('in')
.removeClass('animated')
.removeClass(this.options.attentionAnimation)
.removeClass('modal-overflow')
.attr('aria-hidden', true);
$.support.transition && this.$element.hasClass('fade') ?
this.hideWithTransition() :
this.hideModal();
},
layout: function () {
var prop = this.options.height ? 'height' : 'max-height',
value = this.options.height || this.options.maxHeight;
if (this.options.width){
this.$element.css('width', this.options.width);
var that = this;
this.$element.css('margin-left', function () {
if (/%/ig.test(that.options.width)){
return -(parseInt(that.options.width) / 2) + '%';
} else {
return -($(this).width() / 2) + 'px';
}
});
} else {
this.$element.css('width', '');
this.$element.css('margin-left', '');
}
this.$element.find('.modal-body')
.css('overflow', '')
.css(prop, '');
var modalOverflow = $(window).height() - 10 < this.$element.height();
if (value){
this.$element.find('.modal-body')
.css('overflow', 'auto')
.css(prop, value);
}
if (modalOverflow || this.options.modalOverflow) {
this.$element
.css('margin-top', 0)
.addClass('modal-overflow');
} else {
this.$element
.css('margin-top', 0 - this.$element.height() / 2)
.removeClass('modal-overflow');
}
},
tab: function () {
var that = this;
if (this.isShown && this.options.consumeTab) {
this.$element.on('keydown.tabindex.modal', '[data-tabindex]', function (e) {
if (e.keyCode && e.keyCode == 9){
var $next = $(this),
$rollover = $(this);
that.$element.find('[data-tabindex]:enabled:not([readonly])').each(function (e) {
if (!e.shiftKey){
$next = $next.data('tabindex') < $(this).data('tabindex') ?
$next = $(this) :
$rollover = $(this);
} else {
$next = $next.data('tabindex') > $(this).data('tabindex') ?
$next = $(this) :
$rollover = $(this);
}
});
$next[0] !== $(this)[0] ?
$next.focus() : $rollover.focus();
e.preventDefault();
}
});
} else if (!this.isShown) {
this.$element.off('keydown.tabindex.modal');
}
},
escape: function () {
var that = this;
if (this.isShown && this.options.keyboard) {
if (!this.$element.attr('tabindex')) this.$element.attr('tabindex', -1);
this.$element.on('keyup.dismiss.modal', function (e) {
e.which == 27 && that.hide();
});
} else if (!this.isShown) {
this.$element.off('keyup.dismiss.modal')
}
},
hideWithTransition: function () {
var that = this
, timeout = setTimeout(function () {
that.$element.off($.support.transition.end);
that.hideModal();
}, 500);
this.$element.one($.support.transition.end, function () {
clearTimeout(timeout);
that.hideModal();
});
},
hideModal: function () {
this.$element
.hide()
.trigger('hidden');
var prop = this.options.height ? 'height' : 'max-height';
var value = this.options.height || this.options.maxHeight;
if (value){
this.$element.find('.modal-body')
.css('overflow', '')
.css(prop, '');
}
},
removeLoading: function () {
this.$loading.remove();
this.$loading = null;
this.isLoading = false;
},
loading: function (callback) {
callback = callback || function () {};
var animate = this.$element.hasClass('fade') ? 'fade' : '';
if (!this.isLoading) {
var doAnimate = $.support.transition && animate;
this.$loading = $('<div class="loading-mask ' + animate + '">')
.append(this.options.spinner)
.appendTo(this.$element);
if (doAnimate) this.$loading[0].offsetWidth; // force reflow
this.$loading.addClass('in');
this.isLoading = true;
doAnimate ?
this.$loading.one($.support.transition.end, callback) :
callback();
} else if (this.isLoading && this.$loading) {
this.$loading.removeClass('in');
var that = this;
$.support.transition && this.$element.hasClass('fade')?
this.$loading.one($.support.transition.end, function () { that.removeLoading() }) :
that.removeLoading();
} else if (callback) {
callback(this.isLoading);
}
},
focus: function () {
var $focusElem = this.$element.find(this.options.focusOn);
$focusElem = $focusElem.length ? $focusElem : this.$element;
$focusElem.focus();
},
attention: function (){
// NOTE: transitionEnd with keyframes causes odd behaviour
if (this.options.attentionAnimation){
this.$element
.removeClass('animated')
.removeClass(this.options.attentionAnimation);
var that = this;
setTimeout(function () {
that.$element
.addClass('animated')
.addClass(that.options.attentionAnimation);
}, 0);
}
this.focus();
},
destroy: function () {
var e = $.Event('destroy');
this.$element.trigger(e);
if (e.isDefaultPrevented()) return;
this.teardown();
},
teardown: function () {
if (!this.$parent.length){
this.$element.remove();
this.$element = null;
return;
}
if (this.$parent !== this.$element.parent()){
this.$element.appendTo(this.$parent);
}
this.$element.off('.modal');
this.$element.removeData('modal');
this.$element
.removeClass('in')
.attr('aria-hidden', true);
}
};
/* MODAL PLUGIN DEFINITION
* ======================= */
$.fn.modal = function (option, args) {
return this.each(function () {
var $this = $(this),
data = $this.data('modal'),
options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option);
if (!data) $this.data('modal', (data = new Modal(this, options)));
if (typeof option == 'string') data[option].apply(data, [].concat(args));
else if (options.show) data.show()
})
};
$.fn.modal.defaults = {
keyboard: true,
backdrop: true,
loading: false,
show: true,
width: null,
height: null,
maxHeight: null,
modalOverflow: false,
consumeTab: true,
focusOn: null,
replace: false,
resize: false,
attentionAnimation: 'shake',
manager: 'body',
spinner: '<div class="loading-spinner" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="bar" style="width: 100%;"></div></div></div>'
};
$.fn.modal.Constructor = Modal;
/* MODAL DATA-API
* ============== */
$(function () {
$(document).off('click.modal').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
var $this = $(this),
href = $this.attr('href'),
$target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))), //strip for ie7
option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data());
e.preventDefault();
$target
.modal(option)
.one('hide', function () {
$this.focus();
})
});
});
}(window.jQuery);

View File

@ -0,0 +1,412 @@
/* ===========================================================
* bootstrap-modalmanager.js v2.1
* ===========================================================
* Copyright 2012 Jordan Schroter.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* MODAL MANAGER CLASS DEFINITION
* ====================== */
var ModalManager = function (element, options) {
this.init(element, options);
};
ModalManager.prototype = {
constructor: ModalManager,
init: function (element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn.modalmanager.defaults, this.$element.data(), typeof options == 'object' && options);
this.stack = [];
this.backdropCount = 0;
if (this.options.resize) {
var resizeTimeout,
that = this;
$(window).on('resize.modal', function(){
resizeTimeout && clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function(){
for (var i = 0; i < that.stack.length; i++){
that.stack[i].isShown && that.stack[i].layout();
}
}, 10);
});
}
},
createModal: function (element, options) {
$(element).modal($.extend({ manager: this }, options));
},
appendModal: function (modal) {
this.stack.push(modal);
var that = this;
modal.$element.on('show.modalmanager', targetIsSelf(function (e) {
var showModal = function(){
modal.isShown = true;
var transition = $.support.transition && modal.$element.hasClass('fade');
that.$element
.toggleClass('modal-open', that.hasOpenModal())
.toggleClass('page-overflow', $(window).height() < that.$element.height());
modal.$parent = modal.$element.parent();
modal.$container = that.createContainer(modal);
modal.$element.appendTo(modal.$container);
that.backdrop(modal, function () {
modal.$element.show();
if (transition) {
//modal.$element[0].style.display = 'run-in';
modal.$element[0].offsetWidth;
//modal.$element.one($.support.transition.end, function () { modal.$element[0].style.display = 'block' });
}
modal.layout();
modal.$element
.addClass('in')
.attr('aria-hidden', false);
var complete = function () {
that.setFocus();
modal.$element.trigger('shown');
};
transition ?
modal.$element.one($.support.transition.end, complete) :
complete();
});
};
modal.options.replace ?
that.replace(showModal) :
showModal();
}));
modal.$element.on('hidden.modalmanager', targetIsSelf(function (e) {
that.backdrop(modal);
if (modal.$backdrop){
$.support.transition && modal.$element.hasClass('fade') ?
modal.$backdrop.one($.support.transition.end, function () { that.destroyModal(modal) }) :
that.destroyModal(modal);
} else {
that.destroyModal(modal);
}
}));
modal.$element.on('destroy.modalmanager', targetIsSelf(function (e) {
that.removeModal(modal);
}));
},
destroyModal: function (modal) {
modal.destroy();
var hasOpenModal = this.hasOpenModal();
this.$element.toggleClass('modal-open', hasOpenModal);
if (!hasOpenModal){
this.$element.removeClass('page-overflow');
}
this.removeContainer(modal);
this.setFocus();
},
hasOpenModal: function () {
for (var i = 0; i < this.stack.length; i++){
if (this.stack[i].isShown) return true;
}
return false;
},
setFocus: function () {
var topModal;
for (var i = 0; i < this.stack.length; i++){
if (this.stack[i].isShown) topModal = this.stack[i];
}
if (!topModal) return;
topModal.focus();
},
removeModal: function (modal) {
modal.$element.off('.modalmanager');
if (modal.$backdrop) this.removeBackdrop.call(modal);
this.stack.splice(this.getIndexOfModal(modal), 1);
},
getModalAt: function (index) {
return this.stack[index];
},
getIndexOfModal: function (modal) {
for (var i = 0; i < this.stack.length; i++){
if (modal === this.stack[i]) return i;
}
},
replace: function (callback) {
var topModal;
for (var i = 0; i < this.stack.length; i++){
if (this.stack[i].isShown) topModal = this.stack[i];
}
if (topModal) {
this.$backdropHandle = topModal.$backdrop;
topModal.$backdrop = null;
callback && topModal.$element.one('hidden',
targetIsSelf( $.proxy(callback, this) ));
topModal.hide();
} else if (callback) {
callback();
}
},
removeBackdrop: function (modal) {
modal.$backdrop.remove();
modal.$backdrop = null;
},
createBackdrop: function (animate) {
var $backdrop;
if (!this.$backdropHandle) {
$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(this.$element);
} else {
$backdrop = this.$backdropHandle;
$backdrop.off('.modalmanager');
this.$backdropHandle = null;
this.isLoading && this.removeSpinner();
}
return $backdrop
},
removeContainer: function (modal) {
modal.$container.remove();
modal.$container = null;
},
createContainer: function (modal) {
var $container;
$container = $('<div class="modal-scrollable">')
.css('z-index', getzIndex( 'modal',
modal ? this.getIndexOfModal(modal) : this.stack.length ))
.appendTo(this.$element);
if (modal && modal.options.backdrop != 'static') {
$container.on('click.modal', targetIsSelf(function (e) {
modal.hide();
}));
} else if (modal) {
$container.on('click.modal', targetIsSelf(function (e) {
modal.attention();
}));
}
return $container;
},
backdrop: function (modal, callback) {
var animate = modal.$element.hasClass('fade') ? 'fade' : '',
showBackdrop = modal.options.backdrop &&
this.backdropCount < this.options.backdropLimit;
if (modal.isShown && showBackdrop) {
var doAnimate = $.support.transition && animate && !this.$backdropHandle;
modal.$backdrop = this.createBackdrop(animate);
modal.$backdrop.css('z-index', getzIndex( 'backdrop', this.getIndexOfModal(modal) ));
if (doAnimate) modal.$backdrop[0].offsetWidth; // force reflow
modal.$backdrop.addClass('in');
this.backdropCount += 1;
doAnimate ?
modal.$backdrop.one($.support.transition.end, callback) :
callback();
} else if (!modal.isShown && modal.$backdrop) {
modal.$backdrop.removeClass('in');
this.backdropCount -= 1;
var that = this;
$.support.transition && modal.$element.hasClass('fade')?
modal.$backdrop.one($.support.transition.end, function () { that.removeBackdrop(modal) }) :
that.removeBackdrop(modal);
} else if (callback) {
callback();
}
},
removeSpinner: function(){
this.$spinner && this.$spinner.remove();
this.$spinner = null;
this.isLoading = false;
},
removeLoading: function () {
this.$backdropHandle && this.$backdropHandle.remove();
this.$backdropHandle = null;
this.removeSpinner();
},
loading: function (callback) {
callback = callback || function () { };
this.$element
.toggleClass('modal-open', !this.isLoading || this.hasOpenModal())
.toggleClass('page-overflow', $(window).height() < this.$element.height());
if (!this.isLoading) {
this.$backdropHandle = this.createBackdrop('fade');
this.$backdropHandle[0].offsetWidth; // force reflow
this.$backdropHandle
.css('z-index', getzIndex('backdrop', this.stack.length))
.addClass('in');
var $spinner = $(this.options.spinner)
.css('z-index', getzIndex('modal', this.stack.length))
.appendTo(this.$element)
.addClass('in');
this.$spinner = $(this.createContainer())
.append($spinner)
.on('click.modalmanager', $.proxy(this.loading, this));
this.isLoading = true;
$.support.transition ?
this.$backdropHandle.one($.support.transition.end, callback) :
callback();
} else if (this.isLoading && this.$backdropHandle) {
this.$backdropHandle.removeClass('in');
var that = this;
$.support.transition ?
this.$backdropHandle.one($.support.transition.end, function () { that.removeLoading() }) :
that.removeLoading();
} else if (callback) {
callback(this.isLoading);
}
}
};
/* PRIVATE METHODS
* ======================= */
// computes and caches the zindexes
var getzIndex = (function () {
var zIndexFactor,
baseIndex = {};
return function (type, pos) {
if (typeof zIndexFactor === 'undefined'){
var $baseModal = $('<div class="modal hide" />').appendTo('body'),
$baseBackdrop = $('<div class="modal-backdrop hide" />').appendTo('body');
baseIndex['modal'] = +$baseModal.css('z-index');
baseIndex['backdrop'] = +$baseBackdrop.css('z-index');
zIndexFactor = baseIndex['modal'] - baseIndex['backdrop'];
$baseModal.remove();
$baseBackdrop.remove();
$baseBackdrop = $baseModal = null;
}
return baseIndex[type] + (zIndexFactor * pos);
}
}());
// make sure the event target is the modal itself in order to prevent
// other components such as tabsfrom triggering the modal manager.
// if Boostsrap namespaced events, this would not be needed.
function targetIsSelf(callback){
return function (e) {
if (this === e.target){
return callback.apply(this, arguments);
}
}
}
/* MODAL MANAGER PLUGIN DEFINITION
* ======================= */
$.fn.modalmanager = function (option, args) {
return this.each(function () {
var $this = $(this),
data = $this.data('modalmanager');
if (!data) $this.data('modalmanager', (data = new ModalManager(this, option)));
if (typeof option === 'string') data[option].apply(data, [].concat(args))
})
};
$.fn.modalmanager.defaults = {
backdropLimit: 999,
resize: true,
spinner: '<div class="loading-spinner fade" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="bar" style="width: 100%;"></div></div></div>'
};
$.fn.modalmanager.Constructor = ModalManager
}(jQuery);

361
static/default/js/bootstrap-tooltip.js vendored Normal file
View File

@ -0,0 +1,361 @@
/* ===========================================================
* bootstrap-tooltip.js v2.3.1
* http://twitter.github.com/bootstrap/javascript.html#tooltips
* Inspired by the original jQuery.tipsy by Jason Frame
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* TOOLTIP PUBLIC CLASS DEFINITION
* =============================== */
var Tooltip = function (element, options) {
this.init('tooltip', element, options)
}
Tooltip.prototype = {
constructor: Tooltip
, init: function (type, element, options) {
var eventIn
, eventOut
, triggers
, trigger
, i
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.enabled = true
triggers = this.options.trigger.split(' ')
for (i = triggers.length; i--;) {
trigger = triggers[i]
if (trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (trigger != 'manual') {
eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
}
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
, getOptions: function (options) {
options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay
, hide: options.delay
}
}
return options
}
, enter: function (e) {
var defaults = $.fn[this.type].defaults
, options = {}
, self
this._options && $.each(this._options, function (key, value) {
if (defaults[key] != value) options[key] = value
}, this)
self = $(e.currentTarget)[this.type](options).data(this.type)
if (!self.options.delay || !self.options.delay.show) return self.show()
clearTimeout(this.timeout)
self.hoverState = 'in'
this.timeout = setTimeout(function() {
if (self.hoverState == 'in') self.show()
}, self.options.delay.show)
}
, leave: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (this.timeout) clearTimeout(this.timeout)
if (!self.options.delay || !self.options.delay.hide) return self.hide()
self.hoverState = 'out'
this.timeout = setTimeout(function() {
if (self.hoverState == 'out') self.hide()
}, self.options.delay.hide)
}
, show: function () {
var $tip
, pos
, actualWidth
, actualHeight
, placement
, tp
, e = $.Event('show')
if (this.hasContent() && this.enabled) {
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$tip = this.tip()
this.setContent()
if (this.options.animation) {
$tip.addClass('fade')
}
placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
pos = this.getPosition()
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
switch (placement) {
case 'bottom':
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'top':
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'left':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
break
case 'right':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
break
}
this.applyPlacement(tp, placement)
this.$element.trigger('shown')
}
}
, applyPlacement: function(offset, placement){
var $tip = this.tip()
, width = $tip[0].offsetWidth
, height = $tip[0].offsetHeight
, actualWidth
, actualHeight
, delta
, replace
$tip
.offset(offset)
.addClass(placement)
.addClass('in')
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
if (placement == 'top' && actualHeight != height) {
offset.top = offset.top + height - actualHeight
replace = true
}
if (placement == 'bottom' || placement == 'top') {
delta = 0
if (offset.left < 0){
delta = offset.left * -2
offset.left = 0
$tip.offset(offset)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
}
this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
} else {
this.replaceArrow(actualHeight - height, actualHeight, 'top')
}
if (replace) $tip.offset(offset)
}
, replaceArrow: function(delta, dimension, position){
this
.arrow()
.css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
}
, setContent: function () {
var $tip = this.tip()
, title = this.getTitle()
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
$tip.removeClass('fade in top bottom left right')
}
, hide: function () {
var that = this
, $tip = this.tip()
, e = $.Event('hide')
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$tip.removeClass('in')
function removeWithAnimation() {
var timeout = setTimeout(function () {
$tip.off($.support.transition.end).detach()
}, 500)
$tip.one($.support.transition.end, function () {
clearTimeout(timeout)
$tip.detach()
})
}
$.support.transition && this.$tip.hasClass('fade') ?
removeWithAnimation() :
$tip.detach()
this.$element.trigger('hidden')
return this
}
, fixTitle: function () {
var $e = this.$element
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
}
}
, hasContent: function () {
return this.getTitle()
}
, getPosition: function () {
var el = this.$element[0]
return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
width: el.offsetWidth
, height: el.offsetHeight
}, this.$element.offset())
}
, getTitle: function () {
var title
, $e = this.$element
, o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
return title
}
, tip: function () {
return this.$tip = this.$tip || $(this.options.template)
}
, arrow: function(){
return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
}
, validate: function () {
if (!this.$element[0].parentNode) {
this.hide()
this.$element = null
this.options = null
}
}
, enable: function () {
this.enabled = true
}
, disable: function () {
this.enabled = false
}
, toggleEnabled: function () {
this.enabled = !this.enabled
}
, toggle: function (e) {
var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
self.tip().hasClass('in') ? self.hide() : self.show()
}
, destroy: function () {
this.hide().$element.off('.' + this.type).removeData(this.type)
}
}
/* TOOLTIP PLUGIN DEFINITION
* ========================= */
var old = $.fn.tooltip
$.fn.tooltip = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tooltip')
, options = typeof option == 'object' && option
if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tooltip.Constructor = Tooltip
$.fn.tooltip.defaults = {
animation: true
, placement: 'top'
, selector: false
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
, trigger: 'hover focus'
, title: ''
, delay: 0
, html: false
, container: false
}
/* TOOLTIP NO CONFLICT
* =================== */
$.fn.tooltip.noConflict = function () {
$.fn.tooltip = old
return this
}
}(window.jQuery);

2276
static/default/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

6
static/default/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
static/default/js/jquery-1.7.2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1207
static/default/js/jquery.validate.js vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
static/images/python.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,19 @@
{% extends "default/layout.html" %}
{% block title %}404, Page Not Found{% endblock %}
{% block body %}
<div class="row">
<div class="error">
<h1>404, Page Not Found</h1>
<blockquote>
<p>The requested page,</p>
<p>cannot be found — </p>
<p>We're sorry.</p>
</blockquote>
{% with messages = get_flashed_messages() %}
{% if messages %}<ul class=error>
{% for message in messages %}<li>{{ message }}</li>{% endfor %}
</ul>{% endif %}
{% endwith %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "default/layout.html" %}
{% block title %}500, Internal Server Error{% endblock %}
{% block body %}
<div class="row">
<div class="error">
<h1>500, Internal Server Error</h1>
<blockquote>
<p>The server encountered an unexpected error.</p>
<p>Please try again later.</p>
</blockquote>
{% with messages = get_flashed_messages() %}
{% if messages %}<ul class=error>
{% for message in messages %}<li>{{ message }}</li>{% endfor %}
</ul>{% endif %}
{% endwith %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "default/layout.html" %}
{% block title %}{{ _("Select a cart") }}{% endblock %}
{% block body %}
{% include 'default/nav.html' %}
<div class="row">
<div class="span12">
{% if session.order %}<p>{{ _("Order products by") }}: <strong>{{ session.order }}</strong></p>{% endif %}
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{{ _("Name") }}</th>
<th>{{ _("Baskets") }}</th>
<th>{{ _("User") }}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for cart in carts %}
<tr>
<td>{{ cart.name }}</td>
<td>{{ cart.rows*cart.columns }}</td>
<td>{% if cart.user_id %}{{ cart.user_id }}{% endif %}</td>
<td>{% if not cart.user_id %}<a href="{{ url_for('cart') }}?cart={{ cart.id }}" class="label label-success">{{ _("Get this cart") }}</a>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,28 @@
<div id="help-modal" class="modal hide fade" tabindex="-1"></div>
<script id="help-modal" type="text/javascript">
var $modal = $('#help-modal');
$('.nav .help').on('click', function(){
// create the backdrop and wait for next modal to be triggered
$('body').modalmanager('loading');
setTimeout(function(){
$modal.load('{{ url_for("help") }}', '', function(){
$modal.modal();
});
}, 1000);
});
$modal.on('click', '.update', function(){
$modal.modal('loading');
setTimeout(function(){
$modal
.modal('loading')
.find('.modal-body')
.prepend('<div class="alert alert-info fade in">' +
'Updated!<button type="button" class="close" data-dismiss="alert">&times;</button>' +
'</div>');
}, 1000);
});
</script>

View File

@ -0,0 +1,10 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h1>{{ config['TITLE'] }}</h1>
</div>
<div class="modal-body">
{{ content|safe }}
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn">{{ _("Close") }}</button>
</div>

View File

@ -0,0 +1,37 @@
{% extends "default/layout.html" %}
{% block title %}{{ _("Dashboard") }}{% endblock %}
{% block body %}
{% include 'default/nav.html' %}
<div class="container-fluid">
<div class="subnav">
<ul class="nav nav-pills">
<li><a href="{{ url_for('cart') }}">{% if not session.cart %} {{ _("Select a cart") }} {% else %}{{ _("Change cart") }}{% endif %}</a></li>
<li><a href="{{ url_for('picking') }}">{{ _("Process pickings") }}</a></li>
<li><a href="#" class="help" data-toggle="modal">{{ _("Help") }}</a></li>
</ul>
</div>
</div>
<div class="row index-box">
<div class="span4">
<div class="well">
<h2 class="muted">1. {{ _("Select a cart") }}</h2>
<p>{{ _("Go to your preferences a select a cart do you work. Remember when not working in warehouse, do logout") }}<br/><a class="btn btn-warning" href="{{ url_for('cart') }}">{{ _("Go") }}</a></p>
</div>
</div>
<div class="span4">
<div class="well">
<h2 class="muted">2. {{ _("Process pickings") }}</h2>
<p>{{ _("When process pickings, select products in your warehouse and put in you backet cart.") }}<br/><a class="btn btn-warning" href="{{ url_for('picking') }}">{{ _("Go") }}</a></p>
</div>
</div>
<div class="span4">
<div class="well">
<h2 class="muted">3. {{ _("Make picking") }}</h2>
<p>{{ _("When finish selection products, return your picking area and make delivery package.") }}</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="{{ config['LANGUAGE'] }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{% block description %}{% endblock %}">
<meta name="author" content="{{ config['AUTHOR'] }}">
<title>{% block title %}{% endblock %} - {{ config['TITLE'] }}</title>
<link href="{{ url_for('static', filename='default/css/bootstrap.css') }}" rel="stylesheet" type="text/css">
<link href="{{ url_for('static', filename='default/css/bootstrap-responsive.css') }}" rel="stylesheet" type="text/css">
<link href="{{ url_for('static', filename='default/css/custom.css') }}" rel="stylesheet" type="text/css">
<link href="{{ url_for('static', filename='default/css/bootstrap-modal.css') }}" rel="stylesheet" type="text/css">
<script src="{{ url_for('static', filename='default/js/jquery-1.7.2.min.js') }}" type="text/javascript"></script>
<script src="{{ url_for('static', filename='default/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ url_for('static', filename='default/js/bootstrap-tooltip.js') }}" type="text/javascript"></script>
<script src="{{ url_for('static', filename='default/js/bootstrap-modalmanager.js') }}" type="text/javascript"></script>
<script src="{{ url_for('static', filename='default/js/bootstrap-modal.js') }}" type="text/javascript"></script>
{% block head %}{% endblock %}
</head>
<body>
<div class="container">
{% block body %}{% endblock %}
{% include 'default/footer.html' %}
</div>
</body>

View File

@ -0,0 +1,53 @@
{% extends "default/layout.html" %}
{% block title %}{{ _("Login") }}{% endblock %}
{% block body %}
<script src="{{ url_for('static', filename='default/js/jquery.validate.js') }}" type="text/javascript"></script>
<script type="text/javascript">
$().ready(function() {
$("#login-form").validate({
rules: {
username: "required",
password: "required",
},
messages: {
username: "{{ _('Add a user name') }}",
password: "{{ _('Add a password') }}",
}
});
});
</script>
<div class="login-box">
<form id="login-form" class="login-form" method="POST" action="">
<fieldset>
<div id="legend">
<legend class="">{{ config['TITLE'] }}</legend>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}<ul class=error>
{% for message in messages %}<li>{{ message }}</li>{% endfor %}
</ul>{% endif %}
{% endwith %}
{{ form.csrf_token }}
<div class="control-group">
<label class="control-label" for="username">{{ _("Username") }}</label>
<div class="controls">
{{ form.username(size=20) }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="password">{{ _("Password") }}</label>
<div class="controls">
{{ form.password(size=20) }}
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn btn-large btn-primary" type="submit">{{ _("Login") }}</button>
</div>
</div>
</fieldset>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,46 @@
<div class="navbar navbar-inverse nav">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="/">{{ config['TITLE'] }}</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li class="divider-vertical"></li>
<li><a href="{{ url_for('picking') }}"><i class="icon-pencil icon-white"></i> {{ _("Process pickings") }}</a></li>
</ul>
<div class="pull-right">
<ul class="nav pull-right">
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ _("Welcome") }}, {{ session.username }} {% if session.cart %}- {{ session.cart_name }}{% endif %}<b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="nav-header">{{ _("Order by") }}</a></li>
<li><a href="{{ url_for('cart') }}?order=location">{{ _("Location") }}</a></li>
<li><a href="{{ url_for('cart') }}?order=manufacturer">{{ _("Manufacturer") }}</a></li>
<li class="divider"></li>
<li><a href="{{ url_for('cart') }}"><i class="icon-cog"></i> {% if not session.cart %} {{ _("Select a cart") }} {% else %}{{ _("Change cart") }}{% endif %}</a></li>
<li><a href="#" class="help" data-toggle="modal"><i class="icon-book"></i> {{ _("Help") }}</a></li>
<li><a href="/logout"><i class="icon-off"></i> {{ _("Logout") }}</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% with messages = get_flashed_messages() %}{% if messages %}
<div class="container-fluid">
<div class="row-fluid">
<div class="span12 well">
<ul class=error>
{% for message in messages %}<li>{{ message }}</li>{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}{% endwith %}

View File

@ -0,0 +1,126 @@
{% extends "default/layout.html" %}
{% block title %}{{ _("Select a cart") }}{% endblock %}
{% block head %}<script src="{{ url_for('static', filename='default/js/jquery.validate.js') }}" type="text/javascript"></script>{% endblock %}
{% block body %}
{% include 'default/nav.html' %}
<div class="row">
<div class="span12">
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{{ _("Code") }}</th>
<th>{{ _("Product") }}</th>
<th>{{ _("Qty") }}</th>
<th>{{ _("Qty Available") }}</th>
<th>{{ _("Rack") }}</th>
<th>{{ _("Row") }}</th>
<th>{{ _("Case") }}</th>
</tr>
</thead>
<tbody>
{% if not products %}<tr><td colspan="7">{{ _('There are not pickings to process') }}</td></tr>{% endif %}
{% for product in products %}
<tr class="{% if product.qty_available < 0 %}stock-alert{% endif %}">
<td><strong>{{ product.code }}</strong></td>
<td><a href="#{{ product.id }}" data-toggle="modal">{{ product.name }}</a></td>
<td><strong>{{ product.qty }}</strong></td>
<td>{{ product.qty_available }}</td>
<td>{% if product.loc_rack %}{{ product.loc_rack }}{% endif %}</td>
<td>{% if product.loc_row %}{{ product.loc_row }}{% endif %}</td>
<td>{% if product.loc_case %}{{ product.loc_case }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<form action="#" id="basket-form" class="form-horizontal" method="POST">
{% for product in products %} <div id="{{ product.id }}" class="modal hide fade" tabindex="-1" data-width="760">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>{{ product.code }} - {{ product.name }}</h3>
</div>
<div class="modal-body">
<div class="row-fluid">{% set count = 0 %}{% for cell in range(session.cart_rows*session.cart_columns) %}{% if count < grid|length %}
{% if count is divisibleby session.cart_columns %}{% if not count == 0 %}</div>
<div class="row-fluid">{% endif %}{% endif %}
<div class="span{{ (12 / session.cart_columns)|floatwithoutdecimal }} basket">{% set cell = loop.index %}{% set picking_cell_name = grid|pickingname(cell) %}
<h4>{{ cell }} - {{ picking_cell_name }}</h4>{% for picking in product.pickings %}{% if picking.name == picking_cell_name %}
<p>
<input type="number" name="{{picking.move}}" min="1" value="{% if not product.qty_available < 0 %}{{ picking.qty }}{% endif %}" class="required" required><br/>
{{ _('Qty added in this basket ') }} {% if product.qty_available < 0 %}<span class="stock-alert">({{ _('Qty') }}: {{ picking.qty }})</span>{% endif %}
</p>{% endif %}{% endfor %}
</div>{% set count = count + 1 %}{% endif %}{% endfor %}
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn">Close</button>
</div>
</div>{% endfor %}
<div id="send-process">
<button type="submit" class="btn btn-primary">{{ _('Process') }}</button>
</div>
</form>
{% if grid|length %}<!-- legend -->
<hr/>
<h4>{{ _('Grid Cart') }}</h4>
<div class="row-fluid">{% set count = 0 %}{% for cell in range(session.cart_rows*session.cart_columns) %}{% if count < grid|length %}
{% if count is divisibleby session.cart_columns %}{% if not count == 0 %}</div>
<div class="row-fluid">{% endif %}{% endif %}
<div class="span{{ (12 / session.cart_columns)|floatwithoutdecimal }}">{% set cell = loop.index %}{% set picking_cell_name = grid|pickingname(cell) %}
<strong>{{ cell }} - {{ picking_cell_name }}</strong>
</div>{% set count = count + 1 %}{% endif %}{% endfor %}
</div>{% endif %}
<!-- Modal Window done -->
<div id="send-done" class="modal hide fade" tabindex="-1" data-backdrop="static" data-keyboard="false">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>{{ _('Send quantity products successfully') }}</h3>
</div>
<div class="modal-body">
<p><a href="{{ url_for('picking') }}">{{ _('Get more products/pickings to do') }}</a></p>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn">{{ _('Done') }}</button>
</div>
</div>
<!-- Modal Window error -->
<div id="send-error" class="modal hide fade" tabindex="-1" data-backdrop="static" data-keyboard="false">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>{{ _('Error!') }}</h3>
</div>
<div class="modal-body">
<p>{{ _('Qty are integer values? Review your cart and resend') }}</p>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn">{{ _('Check') }}</button>
</div>
</div>
<script type="text/javascript">
{% if not products %}$("#send-process").hide();{% endif %}
$("#basket-form").submit(function () {
var data = $('#basket-form').serializeArray();
$.ajax({
type: "POST",
url: "/basket",
contentType: "application/json; charset=utf-8",
data:JSON.stringify(data),
success: function(data) {
$('#send-done').modal('show');
$("#send-process").hide();
},
error:function(data){
$('#send-error').modal('show');
},
});
return false;
});
</script>
{% endblock %}