Minor template refactor and cli.py user interface enhancement #6
12
README.md
12
README.md
|
@ -58,12 +58,12 @@ python3 cli.py user add username password
|
||||||
Add some products with:
|
Add some products with:
|
||||||
|
|
||||||
```
|
```
|
||||||
python3 cli.py product add "Birra media" 300
|
python3 cli.py product add "Birra media" 3
|
||||||
python3 cli.py product add "Birra grande" 400
|
python3 cli.py product add "Birra grande" 4
|
||||||
python3 cli.py product add "Cocktail" 500
|
python3 cli.py product add "Cocktail" 5
|
||||||
python3 cli.py product add "Vino" 400
|
python3 cli.py product add "Vino" 4
|
||||||
python3 cli.py product add "Amaro" 200
|
python3 cli.py product add "Amaro" 2
|
||||||
python3 cli.py product add "Acqua" 100
|
python3 cli.py product add "Acqua" 0.5
|
||||||
```
|
```
|
||||||
|
|
||||||
And finally add and event you can play with:
|
And finally add and event you can play with:
|
||||||
|
|
255
cli.py
255
cli.py
|
@ -5,7 +5,8 @@ from datetime import datetime
|
||||||
|
|
||||||
from pos.config import Config
|
from pos.config import Config
|
||||||
from pos.logging import init_logging, get_logger
|
from pos.logging import init_logging, get_logger
|
||||||
from pos.database import Database, User, Event, Product, Transaction
|
from pos.database import Database, User, Event, ProductCategory, Product,\
|
||||||
|
Transaction
|
||||||
|
|
||||||
config = Config()
|
config = Config()
|
||||||
conf_db = config.core['DATABASE']
|
conf_db = config.core['DATABASE']
|
||||||
|
@ -22,7 +23,10 @@ def get_total(transaction):
|
||||||
|
|
||||||
|
|
||||||
def get_income(event):
|
def get_income(event):
|
||||||
|
if event.transactions:
|
||||||
return sum(get_total(t) for t in event.transactions)
|
return sum(get_total(t) for t in event.transactions)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
@ -30,6 +34,14 @@ def cli():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command('initdb')
|
||||||
|
def initdb():
|
||||||
|
with db.get_session() as session:
|
||||||
|
categories = session.query(ProductCategory).count()
|
||||||
|
if not categories:
|
||||||
|
session.add(ProductCategory(name='Default'))
|
||||||
|
|
||||||
|
|
||||||
@cli.group('user')
|
@cli.group('user')
|
||||||
def user():
|
def user():
|
||||||
pass
|
pass
|
||||||
|
@ -49,7 +61,7 @@ def user_add(username, password):
|
||||||
user = User(username=username, password=password)
|
user = User(username=username, password=password)
|
||||||
with db.get_session() as session:
|
with db.get_session() as session:
|
||||||
session.add(user)
|
session.add(user)
|
||||||
print("User succesfully added.")
|
print("User successfully added.")
|
||||||
|
|
||||||
|
|
||||||
@user.command('list')
|
@user.command('list')
|
||||||
|
@ -63,6 +75,26 @@ def user_list():
|
||||||
print("No users found.")
|
print("No users found.")
|
||||||
|
|
||||||
|
|
||||||
|
@user.command('set')
|
||||||
|
@click.option('-p', '--password')
|
||||||
|
@click.argument('user_uid', type=click.INT)
|
||||||
|
def user_set(user_uid, password):
|
||||||
|
with db.get_session() as session:
|
||||||
|
user = session.query(User).get(user_uid)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
print("No user found with id #{}.".format(user_uid))
|
||||||
|
return
|
||||||
|
|
||||||
|
if password:
|
||||||
|
user.password = password
|
||||||
|
|
||||||
|
with db.get_session() as session:
|
||||||
|
session.add(user)
|
||||||
|
|
||||||
|
print("User successfully edited.")
|
||||||
|
|
||||||
|
|
||||||
@cli.group('event')
|
@cli.group('event')
|
||||||
def event():
|
def event():
|
||||||
pass
|
pass
|
||||||
|
@ -78,18 +110,48 @@ def tabulate_events(events):
|
||||||
return tabulate(tab, headers='firstrow')
|
return tabulate(tab, headers='firstrow')
|
||||||
|
|
||||||
|
|
||||||
|
def get_overlapping_events(session, starts_at, ends_at):
|
||||||
|
events = session.query(Event)
|
||||||
|
|
||||||
|
if ends_at is None:
|
||||||
|
events = events.filter(Event.starts_at <= starts_at)
|
||||||
|
else:
|
||||||
|
events = events.filter(Event.ends_at >= starts_at)\
|
||||||
|
.filter(Event.starts_at <= ends_at)
|
||||||
|
|
||||||
|
return events.all()
|
||||||
|
|
||||||
|
|
||||||
@event.command('add')
|
@event.command('add')
|
||||||
@click.argument('name')
|
@click.argument('name')
|
||||||
@click.option('--start')
|
@click.argument('starts_at')
|
||||||
@click.option('--end')
|
@click.argument('ends_at', required=False)
|
||||||
def event_add(name, start, end):
|
def event_add(name, starts_at, ends_at):
|
||||||
start = datetime.strptime(start, "%Y-%m-%d %H:%M")
|
starts_at = datetime.strptime(starts_at, "%Y-%m-%d %H:%M")
|
||||||
end = datetime.strptime(end, "%Y-%m-%d %H:%M")
|
ends_at = (datetime.strptime(ends_at, "%Y-%m-%d %H:%M")
|
||||||
event = Event(name=name, starts_at=start, ends_at=end)
|
if ends_at else None)
|
||||||
|
|
||||||
|
if ends_at and starts_at >= ends_at:
|
||||||
|
print("Could now add event: specified start date ({}) "
|
||||||
|
"is past the end date ({})."
|
||||||
|
.format(starts_at.strftime("%Y-%m-%d %H:%M"),
|
||||||
|
ends_at.strftime("%Y-%m-%d %H:%M")))
|
||||||
|
return
|
||||||
|
|
||||||
with db.get_session() as session:
|
with db.get_session() as session:
|
||||||
|
events = get_overlapping_events(session, starts_at, ends_at)
|
||||||
|
if events:
|
||||||
|
print("Could not add event: another event is overlapping the date "
|
||||||
|
"range you have specified.")
|
||||||
|
print(tabulate_events(events))
|
||||||
|
return
|
||||||
|
|
||||||
|
with db.get_session() as session:
|
||||||
|
event = Event(name=name, starts_at=starts_at, ends_at=ends_at)
|
||||||
session.add(event)
|
session.add(event)
|
||||||
print("Event succesfully added.")
|
session.flush()
|
||||||
|
print("Event successfully added.")
|
||||||
|
print(tabulate_events([event]))
|
||||||
|
|
||||||
|
|
||||||
@event.command('list')
|
@event.command('list')
|
||||||
|
@ -103,32 +165,160 @@ def event_list():
|
||||||
print("No events found.")
|
print("No events found.")
|
||||||
|
|
||||||
|
|
||||||
|
@event.command('set')
|
||||||
|
@click.option('-n', '--name')
|
||||||
|
@click.option('-s', '--start')
|
||||||
|
@click.option('-e', '--end')
|
||||||
|
@click.argument('event_uid')
|
||||||
|
def event_set(event_uid, name, start, end):
|
||||||
|
with db.get_session() as session:
|
||||||
|
event = session.query(Event).get(event_uid)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
print("No event found with id #{}.".format(event_uid))
|
||||||
|
return
|
||||||
|
|
||||||
|
if name:
|
||||||
|
event.name = name
|
||||||
|
|
||||||
|
if start:
|
||||||
|
starts_at = datetime.strptime(start, "%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
if starts_at >= event.ends_at:
|
||||||
|
print("Could not edit event #{}: specified start date ({}) "
|
||||||
|
"is past the end date ({})"
|
||||||
|
.format(event.uid,
|
||||||
|
starts_at.strftime("%Y-%m-%d %H:%M"),
|
||||||
|
event.ends_at.strftime("%Y-%m-%d %H:%M")))
|
||||||
|
return
|
||||||
|
|
||||||
|
event.starts_at = starts_at
|
||||||
|
|
||||||
|
if end:
|
||||||
|
if end == 'none':
|
||||||
|
event.ends_at = None
|
||||||
|
elif end == 'now':
|
||||||
|
event.ends_at = datetime.now()
|
||||||
|
else:
|
||||||
|
ends_at = datetime.strptime(end, "%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
if ends_at <= event.starts_at:
|
||||||
|
print("Could not edit event #{}: specified end date ({}) "
|
||||||
|
"is before the start date ({})"
|
||||||
|
.format(event.uid,
|
||||||
|
ends_at.strftime("%Y-%m-%d %H:%M"),
|
||||||
|
event.starts_at.strftime("%Y-%m-%d %H:%M")))
|
||||||
|
return
|
||||||
|
|
||||||
|
event.ends_at = datetime.strptime(end, "%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
if event.starts_at and event.ends_at:
|
||||||
|
with db.get_session() as session:
|
||||||
|
events = get_overlapping_events(session,
|
||||||
|
event.starts_at, event.ends_at)
|
||||||
|
if events:
|
||||||
|
print("Could not edit event: another event is overlapping the "
|
||||||
|
"date range you have specified.")
|
||||||
|
print(tabulate_events(events))
|
||||||
|
return
|
||||||
|
|
||||||
|
if any([name, start, end]):
|
||||||
|
with db.get_session() as session:
|
||||||
|
session.add(event)
|
||||||
|
session.flush()
|
||||||
|
print("Event successfully edited.")
|
||||||
|
print(tabulate_events([event]))
|
||||||
|
|
||||||
|
|
||||||
|
@cli.group('category')
|
||||||
|
def category():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@category.command('add')
|
||||||
|
@click.option('-s', '--sort', type=click.INT)
|
||||||
|
@click.argument('name')
|
||||||
|
def category_add(name, sort):
|
||||||
|
category = ProductCategory(name=name)
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
category.sort = sort
|
||||||
|
|
||||||
|
with db.get_session() as session:
|
||||||
|
session.add(category)
|
||||||
|
print("Category successfully added.")
|
||||||
|
|
||||||
|
|
||||||
|
def tabulate_categories(categories):
|
||||||
|
tab = [["UID", "Name", "Sort", "Created at"]]
|
||||||
|
for c in categories:
|
||||||
|
tab.append([c.uid, c.name, c.sort, c.created_at])
|
||||||
|
return tabulate(tab, headers='firstrow')
|
||||||
|
|
||||||
|
|
||||||
|
@category.command('list')
|
||||||
|
@click.option('-s', '--sort', is_flag=True)
|
||||||
|
def category_list(sort):
|
||||||
|
with db.get_session() as session:
|
||||||
|
categories = session.query(ProductCategory)
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
categories = categories.order_by(ProductCategory.sort.asc())
|
||||||
|
|
||||||
|
categories = categories.all()
|
||||||
|
|
||||||
|
if categories:
|
||||||
|
print(tabulate_categories(categories))
|
||||||
|
else:
|
||||||
|
print("No categories found.")
|
||||||
|
|
||||||
|
|
||||||
@cli.group('product')
|
@cli.group('product')
|
||||||
def product():
|
def product():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def tabulate_products(products):
|
def tabulate_products(products):
|
||||||
tab = [["UID", "Name", "Price", "Enabled", "Created at"]]
|
tab = [["UID", "Name", "Price", "Sort", "Category",
|
||||||
|
"Enabled", "Created at"]]
|
||||||
for p in products:
|
for p in products:
|
||||||
tab.append([p.uid, p.name, p.price, p.is_active, p.created_at])
|
tab.append([p.uid, p.name, p.price, p.sort, p.category.name,
|
||||||
|
p.is_active, p.created_at])
|
||||||
return tabulate(tab, headers='firstrow')
|
return tabulate(tab, headers='firstrow')
|
||||||
|
|
||||||
|
|
||||||
@product.command('add')
|
@product.command('add')
|
||||||
@click.argument('name')
|
@click.argument('name')
|
||||||
@click.argument('price')
|
@click.argument('price', type=float)
|
||||||
def product_add(name, price):
|
@click.option('-s', '--sort', type=click.INT)
|
||||||
|
@click.option('-c', '--category', type=click.INT)
|
||||||
|
def product_add(name, price, sort, category):
|
||||||
|
price = int(price * 100)
|
||||||
product = Product(name=name, price=price)
|
product = Product(name=name, price=price)
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
product.sort = sort
|
||||||
|
|
||||||
|
if category:
|
||||||
|
product.category_uid = category
|
||||||
|
else:
|
||||||
|
product.category_uid = 1
|
||||||
|
|
||||||
with db.get_session() as session:
|
with db.get_session() as session:
|
||||||
session.add(product)
|
session.add(product)
|
||||||
print("Product succesfully added.")
|
print("Product successfully added.")
|
||||||
|
|
||||||
|
|
||||||
@product.command('list')
|
@product.command('list')
|
||||||
def product_list():
|
@click.option('-s', '--sort', is_flag=True)
|
||||||
|
def product_list(sort):
|
||||||
with db.get_session() as session:
|
with db.get_session() as session:
|
||||||
products = session.query(Product).all()
|
products = session.query(Product)
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
products = products.order_by(Product.sort.asc())
|
||||||
|
|
||||||
|
products = products.all()
|
||||||
|
|
||||||
if products:
|
if products:
|
||||||
print(tabulate_products(products))
|
print(tabulate_products(products))
|
||||||
|
@ -136,6 +326,39 @@ def product_list():
|
||||||
print("No products found.")
|
print("No products found.")
|
||||||
|
|
||||||
|
|
||||||
|
@product.command('set')
|
||||||
|
@click.option('-n', '--name')
|
||||||
|
@click.option('-p', '--price', type=click.INT)
|
||||||
|
@click.option('-s', '--sort', type=click.INT)
|
||||||
|
@click.option('-c', '--category', type=click.INT)
|
||||||
|
@click.argument('product_uid')
|
||||||
|
def product_set(product_uid, name, price, sort, category):
|
||||||
|
with db.get_session() as session:
|
||||||
|
product = session.query(Product).get(product_uid)
|
||||||
|
|
||||||
|
if not product:
|
||||||
|
print("No product found with id #{}.".format(product_uid))
|
||||||
|
return
|
||||||
|
|
||||||
|
if name:
|
||||||
|
product.name = name
|
||||||
|
|
||||||
|
if price:
|
||||||
|
product.price = price
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
product.sort = sort
|
||||||
|
|
||||||
|
if category:
|
||||||
|
product.category_uid = category
|
||||||
|
|
||||||
|
if any([name, price, sort, category]):
|
||||||
|
with db.get_session() as session:
|
||||||
|
session.add(product)
|
||||||
|
print("Event successfully edited.")
|
||||||
|
print(tabulate_products([product]))
|
||||||
|
|
||||||
|
|
||||||
@cli.group('transaction')
|
@cli.group('transaction')
|
||||||
def transaction():
|
def transaction():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -7,3 +7,7 @@ Path = pos.db
|
||||||
|
|
||||||
[FLASK]
|
[FLASK]
|
||||||
SECRET_KEY = CHANGE_ME_NOW!
|
SECRET_KEY = CHANGE_ME_NOW!
|
||||||
|
|
||||||
|
[PRINTER]
|
||||||
|
Host = 192.168.1.100
|
||||||
|
Post = 9100
|
||||||
|
|
|
@ -11,3 +11,7 @@ Password = secret
|
||||||
|
|
||||||
[FLASK]
|
[FLASK]
|
||||||
SECRET_KEY = CHANGE_ME_NOW!
|
SECRET_KEY = CHANGE_ME_NOW!
|
||||||
|
|
||||||
|
[PRINTER]
|
||||||
|
Host = 192.168.1.100
|
||||||
|
Post = 9100
|
||||||
|
|
|
@ -94,9 +94,9 @@ class User(Base):
|
||||||
uid = Column(Integer, primary_key=True)
|
uid = Column(Integer, primary_key=True)
|
||||||
username = Column(String, nullable=False, unique=True)
|
username = Column(String, nullable=False, unique=True)
|
||||||
password = Column(PasswordType(schemes=PASSWORD_SCHEMES), nullable=False)
|
password = Column(PasswordType(schemes=PASSWORD_SCHEMES), nullable=False)
|
||||||
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
|
||||||
is_active = Column(Boolean, nullable=False, server_default='1')
|
is_active = Column(Boolean, nullable=False, server_default='1')
|
||||||
is_authenticated = Column(Boolean, nullable=False, server_default='0')
|
is_authenticated = Column(Boolean, nullable=False, server_default='0')
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return u'{}'.format(self.uid)
|
return u'{}'.format(self.uid)
|
||||||
|
@ -106,20 +106,35 @@ class Event(Base):
|
||||||
__tablename__ = 'events'
|
__tablename__ = 'events'
|
||||||
uid = Column(Integer, primary_key=True)
|
uid = Column(Integer, primary_key=True)
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
|
||||||
starts_at = Column(DateTime, nullable=False, default=datetime.now)
|
starts_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
ends_at = Column(DateTime)
|
ends_at = Column(DateTime)
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
|
|
||||||
transactions = relationship('Transaction', lazy='joined')
|
transactions = relationship('Transaction', lazy='joined')
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCategory(Base):
|
||||||
|
__tablename__ = 'product_categories'
|
||||||
|
uid = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
sort = Column(Integer, nullable=False, server_default='0')
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
|
|
||||||
|
products = relationship('Product', lazy='joined')
|
||||||
|
|
||||||
|
|
||||||
class Product(Base):
|
class Product(Base):
|
||||||
__tablename__ = 'products'
|
__tablename__ = 'products'
|
||||||
uid = Column(Integer, primary_key=True)
|
uid = Column(Integer, primary_key=True)
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
price = Column(Integer, nullable=False)
|
price = Column(Integer, nullable=False)
|
||||||
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
sort = Column(Integer, nullable=False, server_default='0')
|
||||||
|
category_uid = Column(Integer, ForeignKey('product_categories.uid'),
|
||||||
|
nullable=False)
|
||||||
is_active = Column(Boolean, nullable=False, server_default='1')
|
is_active = Column(Boolean, nullable=False, server_default='1')
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
|
|
||||||
|
category = relationship('ProductCategory', lazy='joined')
|
||||||
|
|
||||||
|
|
||||||
order_entry_association = Table(
|
order_entry_association = Table(
|
||||||
|
@ -132,8 +147,8 @@ order_entry_association = Table(
|
||||||
class Transaction(Base):
|
class Transaction(Base):
|
||||||
__tablename__ = 'transactions'
|
__tablename__ = 'transactions'
|
||||||
uid = Column(Integer, primary_key=True)
|
uid = Column(Integer, primary_key=True)
|
||||||
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
|
||||||
event_uid = Column(Integer, ForeignKey('events.uid'), nullable=False)
|
event_uid = Column(Integer, ForeignKey('events.uid'), nullable=False)
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.now)
|
||||||
|
|
||||||
event = relationship('Event')
|
event = relationship('Event')
|
||||||
orders = relationship('Order', lazy='joined',
|
orders = relationship('Order', lazy='joined',
|
||||||
|
|
|
@ -8,3 +8,4 @@ tabulate
|
||||||
PyYAML
|
PyYAML
|
||||||
flask>=0.12.0
|
flask>=0.12.0
|
||||||
flask_login>=0.4.0
|
flask_login>=0.4.0
|
||||||
|
python-escpos
|
||||||
|
|
BIN
static/img/macao-logo-printer.png
Normal file
BIN
static/img/macao-logo-printer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
54
static/js/sell.js
Normal file
54
static/js/sell.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
function updateTotal(amount) {
|
||||||
|
total_el = document.getElementById('total')
|
||||||
|
total = parseFloat(total_el.innerText)
|
||||||
|
total += amount
|
||||||
|
total_el.innerText = total.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBasketItem(uid, name) {
|
||||||
|
return '<button id="basketitem_' + uid + '"' +
|
||||||
|
'onclick="delProduct(' + uid + ')">' +
|
||||||
|
'1 x ' + name +
|
||||||
|
'</button>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function addProduct(uid) {
|
||||||
|
// This is the hidden input element inside the form. We'll use this DOM
|
||||||
|
// element to keep informations about the product, such as the name,
|
||||||
|
// the price and the value inside the 'data-' tag.
|
||||||
|
form_el = document.getElementById('prod_' + uid)
|
||||||
|
basket = document.getElementById('basket')
|
||||||
|
basket_el = document.getElementById('basketitem_' + uid)
|
||||||
|
|
||||||
|
form_el.value = parseInt(form_el.value) + 1
|
||||||
|
|
||||||
|
// If there is not a button for the given product inside the basket
|
||||||
|
// div we'll create it.
|
||||||
|
if (basket_el === null) {
|
||||||
|
basket.innerHTML += renderBasketItem(uid, form_el.dataset.name)
|
||||||
|
} else {
|
||||||
|
// Otherwise we'll just update it.
|
||||||
|
console.log(form_el.value)
|
||||||
|
new_text = form_el.value + ' x ' + form_el.dataset.name
|
||||||
|
basket_el.innerText = new_text
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTotal(parseFloat(form_el.dataset.price))
|
||||||
|
}
|
||||||
|
|
||||||
|
function delProduct(uid) {
|
||||||
|
form_el = document.getElementById('prod_' + uid)
|
||||||
|
basket = document.getElementById('basket')
|
||||||
|
basket_el = document.getElementById('basketitem_' + uid)
|
||||||
|
|
||||||
|
form_el.value = parseInt(form_el.value) - 1
|
||||||
|
|
||||||
|
if (form_el.value == 0) {
|
||||||
|
basket.removeChild(basket_el)
|
||||||
|
} else {
|
||||||
|
new_text = form_el.value + ' x ' + form_el.dataset.name
|
||||||
|
basket_el.innerText = new_text
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTotal(parseFloat(-form_el.dataset.price))
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
html {
|
html {
|
||||||
width: 100%;
|
font-size: 14px;
|
||||||
height: 100%;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -11,7 +9,7 @@ body {
|
||||||
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: 2rem;
|
margin: 1rem 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
|
@ -41,12 +39,6 @@ h1 {
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-size: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -120,8 +112,8 @@ form#login > button:hover {
|
||||||
|
|
||||||
#products {
|
#products {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 4rem auto;
|
margin: 0.5rem auto;
|
||||||
max-width: 50%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#products > button {
|
#products > button {
|
||||||
|
@ -138,33 +130,38 @@ form#login > button:hover {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#basket {
|
#summary {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
float: right;
|
float: right;
|
||||||
margin: 4rem auto;
|
margin: 0.5rem auto;
|
||||||
min-width: 30%;
|
}
|
||||||
|
|
||||||
|
#basket {
|
||||||
|
margin: 2rem auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#basket > button {
|
#basket > button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 15rem;
|
width: 20rem;
|
||||||
height: 3rem;
|
height: 4rem;
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: 0.3rem;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0.2rem 0.5rem;
|
||||||
font-size: 1.3rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sell {
|
#sell {
|
||||||
display: inline-block;
|
margin: 1rem auto;
|
||||||
float: right;
|
|
||||||
margin: auto;
|
|
||||||
min-width: 30%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#sell > button {
|
#sell > button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 15rem;
|
width: 20rem;
|
||||||
height: 3rem;
|
height: 5rem;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0.2rem 0.5rem;
|
||||||
font-size: 1.3rem;
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sell > p {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,36 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1>{{ title }}</h1>
|
|
||||||
{% if event %}
|
|
||||||
<h2>{{ event.name }}</h2>
|
|
||||||
<h3>{{ event.starts_at }} → {{ event.ends_at }}</h3>
|
|
||||||
{% else %}
|
|
||||||
<h2>No ongoing event!</h2>
|
|
||||||
{% endif %}
|
|
||||||
<div id="products">
|
<div id="products">
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onclick="addProduct({{ product.uid }})">
|
onclick="addProduct({{ product.uid }})">
|
||||||
<span class="name">{{ product.name }}</span>
|
<span class="name">{{ product.name }}</span>
|
||||||
<span class="price">{{ product.price }}</span>
|
<span class="price">
|
||||||
|
€ {{ '{0:.02f}'.format(product.price / 100.0) }}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div id="basket">
|
<div id="summary">
|
||||||
</div>
|
<div id="basket"></div>
|
||||||
<form id="sell" action="/sell" method="POST">
|
<form id="sell" action="/sell" method="POST">
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
id="prod_{{ product.uid }}"
|
id="prod_{{ product.uid }}"
|
||||||
name="{{ product.uid }}"
|
name="{{ product.uid }}"
|
||||||
data-name="{{ product.name }}"
|
data-name="{{ product.name }}"
|
||||||
data-price="{{ product.price }}"
|
data-price="{{ '{0:.02f}'.format(product.price / 100.0) }}"
|
||||||
value="0">
|
value="0">
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<p>Total: € <span id="total">0.00</span></p>
|
||||||
<button type="submit">Print</input>
|
<button type="submit">Print</input>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='js/sell.js') }}">
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function addProduct(uid) {
|
{%- for product in products %}
|
||||||
// This is the hidden input element inside the form. We'll use this DOM
|
document.getElementById('prod_{{ product.uid }}').value = 0
|
||||||
// element to keep informations about the product, such as the name,
|
{%- endfor %}
|
||||||
// the price and the value inside the 'data-' tag.
|
|
||||||
form_el = document.getElementById('prod_' + uid)
|
|
||||||
basket = document.getElementById('basket')
|
|
||||||
basket_el = document.getElementById('basketitem_' + uid)
|
|
||||||
|
|
||||||
form_el.value = parseInt(form_el.value) + 1
|
|
||||||
|
|
||||||
// If there is not a button for the given product inside the basket
|
|
||||||
// div we'll create it.
|
|
||||||
if (basket_el === null) {
|
|
||||||
new_basket_el =
|
|
||||||
'<button id="basketitem_' + uid + '"' +
|
|
||||||
'onclick="delProduct(' + uid + ')">' +
|
|
||||||
'1 x ' + form_el.dataset.name +
|
|
||||||
'</button>'
|
|
||||||
basket.innerHTML += new_basket_el
|
|
||||||
} else {
|
|
||||||
// Otherwise we'll just update it.
|
|
||||||
console.log(form_el.value)
|
|
||||||
new_text = form_el.value + ' x ' + form_el.dataset.name
|
|
||||||
basket_el.innerText = new_text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function delProduct(uid) {
|
|
||||||
form_el = document.getElementById('prod_' + uid)
|
|
||||||
basket = document.getElementById('basket')
|
|
||||||
basket_el = document.getElementById('basketitem_' + uid)
|
|
||||||
|
|
||||||
form_el.value = parseInt(form_el.value) - 1
|
|
||||||
|
|
||||||
if (form_el.value == 0) {
|
|
||||||
basket.removeChild(basket_el)
|
|
||||||
} else {
|
|
||||||
new_text = form_el.value + ' x ' + form_el.dataset.name
|
|
||||||
basket_el.innerText = new_text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
62
web.py
62
web.py
|
@ -1,4 +1,6 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from time import sleep
|
||||||
|
import escpos.printer
|
||||||
|
|
||||||
from flask import Flask, redirect, request, render_template, flash
|
from flask import Flask, redirect, request, render_template, flash
|
||||||
from flask_login import LoginManager, login_user, logout_user, login_required
|
from flask_login import LoginManager, login_user, logout_user, login_required
|
||||||
|
@ -11,11 +13,20 @@ from pos.database import Database, User, Product, Event, Transaction, Order
|
||||||
config = Config()
|
config = Config()
|
||||||
debug = config.core['GENERAL'].getboolean('Debug', False)
|
debug = config.core['GENERAL'].getboolean('Debug', False)
|
||||||
conf_db = config.core['DATABASE']
|
conf_db = config.core['DATABASE']
|
||||||
|
conf_printer = config.core['PRINTER']
|
||||||
conf_flask = config.core['FLASK']
|
conf_flask = config.core['FLASK']
|
||||||
|
|
||||||
init_logging(config.logging)
|
init_logging(config.logging)
|
||||||
log = get_logger('web')
|
log = get_logger('web')
|
||||||
|
|
||||||
|
printer = escpos.printer.Network(
|
||||||
|
conf_printer['Host'],
|
||||||
|
port=conf_printer.getint('Port'),
|
||||||
|
timeout=15)
|
||||||
|
|
||||||
|
|
||||||
|
db = Database(**conf_db)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.update(
|
app.config.update(
|
||||||
debug=debug,
|
debug=debug,
|
||||||
|
@ -27,8 +38,6 @@ login_manager.init_app(app)
|
||||||
login_manager.login_view = 'login'
|
login_manager.login_view = 'login'
|
||||||
login_manager.login_message_category = 'error'
|
login_manager.login_message_category = 'error'
|
||||||
|
|
||||||
db = Database(**conf_db)
|
|
||||||
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(uid):
|
def load_user(uid):
|
||||||
|
@ -87,14 +96,15 @@ def logout():
|
||||||
def get_products(session):
|
def get_products(session):
|
||||||
return session.query(Product)\
|
return session.query(Product)\
|
||||||
.filter(Product.is_active == 1)\
|
.filter(Product.is_active == 1)\
|
||||||
|
.order_by(Product.sort.asc())\
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
|
|
||||||
def get_event(session):
|
def get_event(session):
|
||||||
return session.query(Event)\
|
return session.query(Event)\
|
||||||
.filter(Event.starts_at < datetime.now())\
|
.filter(Event.starts_at < datetime.now())\
|
||||||
.filter((Event.ends_at > datetime.now()) |
|
.filter((Event.ends_at.is_(None))
|
||||||
(Event.ends_at is None))\
|
| (Event.ends_at > datetime.now()))\
|
||||||
.one_or_none()
|
.one_or_none()
|
||||||
|
|
||||||
|
|
||||||
|
@ -109,6 +119,48 @@ def sell_page():
|
||||||
title='Sell', event=event, products=products)
|
title='Sell', event=event, products=products)
|
||||||
|
|
||||||
|
|
||||||
|
def print_orders(transaction, cat, orders):
|
||||||
|
printer.open()
|
||||||
|
|
||||||
|
printer.set(align='CENTER')
|
||||||
|
printer.image('static/img/macao-logo-printer.png', impl='bitImageColumn')
|
||||||
|
|
||||||
|
printer.set(align='CENTER', text_type='B')
|
||||||
|
printer.text(transaction.event.name.upper())
|
||||||
|
printer.text("\n\n")
|
||||||
|
|
||||||
|
for o in orders:
|
||||||
|
printer.set(align='LEFT', width=2, height=2)
|
||||||
|
printer.text("{} x {}".format(o.quantity, o.product.name.upper()))
|
||||||
|
printer.text("\n")
|
||||||
|
printer.text("\n")
|
||||||
|
|
||||||
|
printer.set(align='RIGHT')
|
||||||
|
printer.text("{} #{}-{}-{}"
|
||||||
|
.format(datetime.strftime(transaction.created_at,
|
||||||
|
"%Y-%m-%d %H:%M"),
|
||||||
|
transaction.event.uid, transaction.uid, cat))
|
||||||
|
|
||||||
|
printer.cut()
|
||||||
|
printer.close()
|
||||||
|
sleep(0.7)
|
||||||
|
|
||||||
|
|
||||||
|
def print_transaction(transaction):
|
||||||
|
categorized_orders = {}
|
||||||
|
|
||||||
|
for o in transaction.orders:
|
||||||
|
uid = o.product.category_uid
|
||||||
|
|
||||||
|
if not categorized_orders.get(uid, False):
|
||||||
|
categorized_orders[uid] = []
|
||||||
|
|
||||||
|
categorized_orders[uid].append(o)
|
||||||
|
|
||||||
|
for cat, orders in categorized_orders.items():
|
||||||
|
print_orders(transaction, cat, orders)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/sell', methods=['POST'])
|
@app.route('/sell', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def sell():
|
def sell():
|
||||||
|
@ -134,6 +186,8 @@ def sell():
|
||||||
|
|
||||||
transaction = Transaction(event_uid=event.uid, orders=orders)
|
transaction = Transaction(event_uid=event.uid, orders=orders)
|
||||||
session.add(transaction)
|
session.add(transaction)
|
||||||
|
session.flush()
|
||||||
|
print_transaction(transaction)
|
||||||
|
|
||||||
flash("Success!", 'success')
|
flash("Success!", 'success')
|
||||||
return sell_page()
|
return sell_page()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user