From a29ec236f68bf603302427ea91cfe73d439a077b Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 15:08:28 +0100 Subject: [PATCH 01/15] Implement new features for cly.py 'event' command. * The user can now create a new event with a not defined end date. * The user can now edit the name and the start and end date of a given event. * The software can now check if the event the user is creating or editing is overlapping another event already present in the database. * The software will allow the shortcut keywords 'now' and 'none' in the 'event set --end' command. --- cli.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 6 deletions(-) diff --git a/cli.py b/cli.py index aa82a73..e2cd7e4 100755 --- a/cli.py +++ b/cli.py @@ -78,18 +78,48 @@ def tabulate_events(events): return tabulate(tab, headers='firstrow') +def get_overlapping_events(session, starts_at, ends_at): + q = session.query(Event) + + if ends_at is None: + q = q.filter(Event.starts_at <= starts_at) + else: + q = q.filter(Event.ends_at >= starts_at)\ + .filter(Event.starts_at <= ends_at) + + return q.all() + + @event.command('add') @click.argument('name') -@click.option('--start') -@click.option('--end') -def event_add(name, start, end): - start = datetime.strptime(start, "%Y-%m-%d %H:%M") - end = datetime.strptime(end, "%Y-%m-%d %H:%M") - event = Event(name=name, starts_at=start, ends_at=end) +@click.argument('starts_at') +@click.argument('ends_at', required=False) +def event_add(name, starts_at, ends_at): + starts_at = datetime.strptime(starts_at, "%Y-%m-%d %H:%M") + ends_at = (datetime.strptime(ends_at, "%Y-%m-%d %H:%M") + 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: + 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.flush() print("Event succesfully added.") + print(tabulate_events([event])) @event.command('list') @@ -103,6 +133,71 @@ def event_list(): 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 succesfully edited.") + print(tabulate_events([event])) + + @cli.group('product') def product(): pass -- 2.30.2 From e2badf6b537f8344edf9863c23742e4388c6bf6b Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 15:54:08 +0100 Subject: [PATCH 02/15] Implement 'user set' command in cli.py. --- cli.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cli.py b/cli.py index e2cd7e4..4d55ba1 100755 --- a/cli.py +++ b/cli.py @@ -63,6 +63,26 @@ def user_list(): 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') def event(): pass -- 2.30.2 From 10bd8a16606897bb05bbba49797876f55bcce82a Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 18:56:06 +0100 Subject: [PATCH 03/15] Fix typos in cli.py. --- cli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli.py b/cli.py index 4d55ba1..643ccb9 100755 --- a/cli.py +++ b/cli.py @@ -49,7 +49,7 @@ def user_add(username, password): user = User(username=username, password=password) with db.get_session() as session: session.add(user) - print("User succesfully added.") + print("User successfully added.") @user.command('list') @@ -138,7 +138,7 @@ def event_add(name, starts_at, ends_at): event = Event(name=name, starts_at=starts_at, ends_at=ends_at) session.add(event) session.flush() - print("Event succesfully added.") + print("Event successfully added.") print(tabulate_events([event])) @@ -214,7 +214,7 @@ def event_set(event_uid, name, start, end): with db.get_session() as session: session.add(event) session.flush() - print("Event succesfully edited.") + print("Event successfully edited.") print(tabulate_events([event])) @@ -237,7 +237,7 @@ def product_add(name, price): product = Product(name=name, price=price) with db.get_session() as session: session.add(product) - print("Product succesfully added.") + print("Product successfully added.") @product.command('list') -- 2.30.2 From c07c19ca32f0e54744baf0314c4c467a4c1f9e5e Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 19:23:37 +0100 Subject: [PATCH 04/15] Database schema change and new UI feature. The user can now rearrange the products. * Product has a new 'order' column. * The user can now specify the sort order at product creation time with the 'product add --sort INT' command. * The user can now rearrange the products with the 'products set --sort INT' comand. * The user can now show a sorted list of products in cli.py with the 'product list --sort' command. --- cli.py | 50 ++++++++++++++++++++++++++++++++++++++++++++----- pos/database.py | 1 + web.py | 1 + 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/cli.py b/cli.py index 643ccb9..61ab867 100755 --- a/cli.py +++ b/cli.py @@ -224,26 +224,37 @@ def product(): def tabulate_products(products): - tab = [["UID", "Name", "Price", "Enabled", "Created at"]] + tab = [["UID", "Name", "Price", "Sort", "Enabled", "Created at"]] 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.is_active, p.created_at]) return tabulate(tab, headers='firstrow') @product.command('add') @click.argument('name') @click.argument('price') -def product_add(name, price): +@click.option('-s', '--sort', type=click.INT) +def product_add(name, price, sort): product = Product(name=name, price=price) + + if sort: + product.sort = sort + with db.get_session() as session: session.add(product) print("Product successfully added.") @product.command('list') -def product_list(): +@click.option('-s', '--sort', is_flag=True) +def product_list(sort): 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: print(tabulate_products(products)) @@ -251,6 +262,35 @@ def product_list(): 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.argument('product_uid') +def product_set(product_uid, name, price, sort): + 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 any([name, price, sort]): + with db.get_session() as session: + session.add(product) + print("Event successfully edited.") + print(tabulate_products([product])) + + @cli.group('transaction') def transaction(): pass diff --git a/pos/database.py b/pos/database.py index cd5576a..4491905 100644 --- a/pos/database.py +++ b/pos/database.py @@ -118,6 +118,7 @@ class Product(Base): uid = Column(Integer, primary_key=True) name = Column(String, nullable=False) price = Column(Integer, nullable=False) + sort = Column(Integer, nullable=False, server_default='0') created_at = Column(DateTime, nullable=False, default=datetime.now) is_active = Column(Boolean, nullable=False, server_default='1') diff --git a/web.py b/web.py index cc8429e..96b0878 100644 --- a/web.py +++ b/web.py @@ -87,6 +87,7 @@ def logout(): def get_products(session): return session.query(Product)\ .filter(Product.is_active == 1)\ + .order_by(Product.sort.asc())\ .all() -- 2.30.2 From 5db877dc4c2b851a2209150f1c8f00b9a000ff6f Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 19:39:05 +0100 Subject: [PATCH 05/15] Minor cli.py refactoring. --- cli.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli.py b/cli.py index 61ab867..00bc4c0 100755 --- a/cli.py +++ b/cli.py @@ -99,15 +99,15 @@ def tabulate_events(events): def get_overlapping_events(session, starts_at, ends_at): - q = session.query(Event) + events = session.query(Event) if ends_at is None: - q = q.filter(Event.starts_at <= starts_at) + events = events.filter(Event.starts_at <= starts_at) else: - q = q.filter(Event.ends_at >= starts_at)\ - .filter(Event.starts_at <= ends_at) + events = events.filter(Event.ends_at >= starts_at)\ + .filter(Event.starts_at <= ends_at) - return q.all() + return events.all() @event.command('add') -- 2.30.2 From 9968cd2b8b047257f5f6474f5b657c5eb67c4e87 Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 19:42:21 +0100 Subject: [PATCH 06/15] Database schema change. Minor database refactoring. --- pos/database.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pos/database.py b/pos/database.py index 4491905..6e17049 100644 --- a/pos/database.py +++ b/pos/database.py @@ -94,9 +94,9 @@ class User(Base): uid = Column(Integer, primary_key=True) username = Column(String, nullable=False, unique=True) 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_authenticated = Column(Boolean, nullable=False, server_default='0') + created_at = Column(DateTime, nullable=False, default=datetime.now) def get_id(self): return u'{}'.format(self.uid) @@ -106,9 +106,9 @@ class Event(Base): __tablename__ = 'events' uid = Column(Integer, primary_key=True) name = Column(String, nullable=False) - created_at = Column(DateTime, nullable=False, default=datetime.now) starts_at = Column(DateTime, nullable=False, default=datetime.now) ends_at = Column(DateTime) + created_at = Column(DateTime, nullable=False, default=datetime.now) transactions = relationship('Transaction', lazy='joined') @@ -119,8 +119,8 @@ class Product(Base): name = Column(String, nullable=False) price = Column(Integer, nullable=False) sort = Column(Integer, nullable=False, server_default='0') - created_at = Column(DateTime, nullable=False, default=datetime.now) is_active = Column(Boolean, nullable=False, server_default='1') + created_at = Column(DateTime, nullable=False, default=datetime.now) order_entry_association = Table( @@ -133,8 +133,8 @@ order_entry_association = Table( class Transaction(Base): __tablename__ = 'transactions' uid = Column(Integer, primary_key=True) - created_at = Column(DateTime, nullable=False, default=datetime.now) event_uid = Column(Integer, ForeignKey('events.uid'), nullable=False) + created_at = Column(DateTime, nullable=False, default=datetime.now) event = relationship('Event') orders = relationship('Order', lazy='joined', -- 2.30.2 From 6c5e5568ea646231e160fef29671d20e823fc093 Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 21:04:10 +0100 Subject: [PATCH 07/15] Database schema change. Implement ProductCategory. * The user can now create product categories with the 'category add' command. * The user can now add products to categories with the 'product add --category INT' command. --- cli.py | 76 +++++++++++++++++++++++++++++++++++++++++++++---- pos/database.py | 14 +++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/cli.py b/cli.py index 00bc4c0..fdb4315 100755 --- a/cli.py +++ b/cli.py @@ -5,7 +5,8 @@ from datetime import datetime from pos.config import Config 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() conf_db = config.core['DATABASE'] @@ -30,6 +31,14 @@ def cli(): 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') def user(): pass @@ -218,15 +227,60 @@ def event_set(event_uid, name, start, end): 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') def product(): pass def tabulate_products(products): - tab = [["UID", "Name", "Price", "Sort", "Enabled", "Created at"]] + tab = [["UID", "Name", "Price", "Sort", "Category", + "Enabled", "Created at"]] for p in products: - tab.append([p.uid, p.name, p.price, p.sort, 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') @@ -234,12 +288,18 @@ def tabulate_products(products): @click.argument('name') @click.argument('price') @click.option('-s', '--sort', type=click.INT) -def product_add(name, price, sort): +@click.option('-c', '--category', type=click.INT) +def product_add(name, price, sort, category): 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: session.add(product) print("Product successfully added.") @@ -266,8 +326,9 @@ def product_list(sort): @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): +def product_set(product_uid, name, price, sort, category): with db.get_session() as session: product = session.query(Product).get(product_uid) @@ -284,7 +345,10 @@ def product_set(product_uid, name, price, sort): if sort: product.sort = sort - if any([name, price, 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.") diff --git a/pos/database.py b/pos/database.py index 6e17049..1f7daa0 100644 --- a/pos/database.py +++ b/pos/database.py @@ -113,15 +113,29 @@ class Event(Base): 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): __tablename__ = 'products' uid = Column(Integer, primary_key=True) name = Column(String, nullable=False) price = Column(Integer, nullable=False) 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') created_at = Column(DateTime, nullable=False, default=datetime.now) + category = relationship('ProductCategory', lazy='joined') + order_entry_association = Table( 'order_entry_associations', Base.metadata, -- 2.30.2 From 8c4540e82cac92a732fd328d12e2f002c44362f9 Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 21:08:32 +0100 Subject: [PATCH 08/15] Fix exception raising on 'event list' command when event has no transactions. --- cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli.py b/cli.py index fdb4315..d7c6106 100755 --- a/cli.py +++ b/cli.py @@ -23,7 +23,10 @@ def get_total(transaction): def get_income(event): - return sum(get_total(t) for t in event.transactions) + if event.transactions: + return sum(get_total(t) for t in event.transactions) + else: + return 0 @click.group() -- 2.30.2 From 67ae28fcac0ee3ae841a35c55c0286fb9c9f1ac4 Mon Sep 17 00:00:00 2001 From: crudo Date: Fri, 24 Mar 2017 23:03:13 +0100 Subject: [PATCH 09/15] Fix query bug that prevented events with undefined end date being used. --- web.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web.py b/web.py index 96b0878..b9c1584 100644 --- a/web.py +++ b/web.py @@ -94,8 +94,8 @@ def get_products(session): def get_event(session): return session.query(Event)\ .filter(Event.starts_at < datetime.now())\ - .filter((Event.ends_at > datetime.now()) | - (Event.ends_at is None))\ + .filter((Event.ends_at.is_(None)) + | (Event.ends_at > datetime.now()))\ .one_or_none() -- 2.30.2 From 389ad04e1998769f0b6fc14153bdbad60c49675d Mon Sep 17 00:00:00 2001 From: crudo Date: Sat, 25 Mar 2017 02:38:02 +0100 Subject: [PATCH 10/15] Implement transaction printing. --- docs/config_core/core_debug_sqlite.ini | 4 ++ docs/config_core/core_production_mysql.ini | 4 ++ requirements.txt | 1 + web.py | 52 +++++++++++++++++++++- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/docs/config_core/core_debug_sqlite.ini b/docs/config_core/core_debug_sqlite.ini index 43f365e..2f847e3 100644 --- a/docs/config_core/core_debug_sqlite.ini +++ b/docs/config_core/core_debug_sqlite.ini @@ -7,3 +7,7 @@ Path = pos.db [FLASK] SECRET_KEY = CHANGE_ME_NOW! + +[PRINTER] +Host = 192.168.1.100 +Post = 9100 diff --git a/docs/config_core/core_production_mysql.ini b/docs/config_core/core_production_mysql.ini index af4207c..6ef2a25 100644 --- a/docs/config_core/core_production_mysql.ini +++ b/docs/config_core/core_production_mysql.ini @@ -11,3 +11,7 @@ Password = secret [FLASK] SECRET_KEY = CHANGE_ME_NOW! + +[PRINTER] +Host = 192.168.1.100 +Post = 9100 diff --git a/requirements.txt b/requirements.txt index 66f9508..920c23a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ tabulate PyYAML flask>=0.12.0 flask_login>=0.4.0 +python-escpos diff --git a/web.py b/web.py index b9c1584..6457edb 100644 --- a/web.py +++ b/web.py @@ -1,4 +1,6 @@ from datetime import datetime +from time import sleep +import escpos.printer from flask import Flask, redirect, request, render_template, flash 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() debug = config.core['GENERAL'].getboolean('Debug', False) conf_db = config.core['DATABASE'] +conf_printer = config.core['PRINTER'] conf_flask = config.core['FLASK'] init_logging(config.logging) 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.config.update( debug=debug, @@ -27,8 +38,6 @@ login_manager.init_app(app) login_manager.login_view = 'login' login_manager.login_message_category = 'error' -db = Database(**conf_db) - @login_manager.user_loader def load_user(uid): @@ -110,6 +119,43 @@ def sell_page(): title='Sell', event=event, products=products) +def print_orders(event, 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(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.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.event, orders) + + @app.route('/sell', methods=['POST']) @login_required def sell(): @@ -135,6 +181,8 @@ def sell(): transaction = Transaction(event_uid=event.uid, orders=orders) session.add(transaction) + session.flush() + print_transaction(transaction) flash("Success!", 'success') return sell_page() -- 2.30.2 From 1871221f04b1e54b0c678e8324d89d9507febdf9 Mon Sep 17 00:00:00 2001 From: crudo Date: Sat, 25 Mar 2017 02:57:22 +0100 Subject: [PATCH 11/15] Include UID in ticket. --- web.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/web.py b/web.py index 6457edb..39bd095 100644 --- a/web.py +++ b/web.py @@ -119,23 +119,28 @@ def sell_page(): title='Sell', event=event, products=products) -def print_orders(event, orders): +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(event.name.upper()) + 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) @@ -153,7 +158,7 @@ def print_transaction(transaction): categorized_orders[uid].append(o) for cat, orders in categorized_orders.items(): - print_orders(transaction.event, orders) + print_orders(transaction, cat, orders) @app.route('/sell', methods=['POST']) -- 2.30.2 From 2ea448d03959e0aa302fa982351f6efb3240ebbc Mon Sep 17 00:00:00 2001 From: crudo Date: Sat, 25 Mar 2017 20:22:36 +0100 Subject: [PATCH 12/15] Add Macao logo for printer. --- static/img/macao-logo-printer.png | Bin 0 -> 5426 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/img/macao-logo-printer.png diff --git a/static/img/macao-logo-printer.png b/static/img/macao-logo-printer.png new file mode 100644 index 0000000000000000000000000000000000000000..05d3042b681b6adf97d7588f595f8f2a0e689422 GIT binary patch literal 5426 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 E2ft7=2mk;8 literal 0 HcmV?d00001 -- 2.30.2 From 2d3bda83288c649836f865db30f6c09c8a37aa6f Mon Sep 17 00:00:00 2001 From: crudo Date: Sat, 25 Mar 2017 21:47:42 +0100 Subject: [PATCH 13/15] Break the borders edition. --- static/style/screen.css | 47 +++++++++++++++++-------------------- templates/sell.html | 52 +++++++++++++++++++++++++++-------------- 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/static/style/screen.css b/static/style/screen.css index dc03d02..90c8ce7 100644 --- a/static/style/screen.css +++ b/static/style/screen.css @@ -1,7 +1,5 @@ html { - width: 100%; - height: 100%; - font-size: 16px; + font-size: 14px; } body { @@ -11,7 +9,7 @@ body { main { - margin: 2rem; + margin: 1rem 2rem; } .alert { @@ -41,12 +39,6 @@ h1 { h2 { margin-bottom: 0.5rem; - font-size: 2rem; - text-align: center; -} - -h3 { - margin-bottom: 0.2rem; font-size: 1.5rem; text-align: center; } @@ -120,8 +112,8 @@ form#login > button:hover { #products { display: inline-block; - margin: 4rem auto; - max-width: 50%; + margin: 0.5rem auto; + width: 60%; } #products > button { @@ -138,33 +130,38 @@ form#login > button:hover { display: block; } -#basket { +#summary { display: inline-block; float: right; - margin: 4rem auto; - min-width: 30%; + margin: 0.5rem auto; +} + +#basket { + margin: 2rem auto; } #basket > button { display: block; - width: 15rem; - height: 3rem; + width: 20rem; + height: 4rem; margin-bottom: 0.3rem; padding: 0.2rem 0.5rem; - font-size: 1.3rem; + font-size: 1.5rem; } #sell { - display: inline-block; - float: right; - margin: auto; - min-width: 30%; + margin: 1rem auto; } #sell > button { display: block; - width: 15rem; - height: 3rem; + width: 20rem; + height: 5rem; padding: 0.2rem 0.5rem; - font-size: 1.3rem; + font-size: 2rem; +} + +#sell > p { + font-size: 2rem; + margin-bottom: 0.5rem; } diff --git a/templates/sell.html b/templates/sell.html index 8445dfb..a87cf0f 100644 --- a/templates/sell.html +++ b/templates/sell.html @@ -1,36 +1,46 @@ {% extends 'base.html' %} {% block main %} -

{{ title }}

-{% if event %} -

{{ event.name }}

-

{{ event.starts_at }} → {{ event.ends_at }}

-{% else %} -

No ongoing event!

-{% endif %}
{% for product in products %} {% endfor %}
-
-
-
+
+
+ {% for product in products %} - + {% endfor %} -
{% endblock %} -- 2.30.2 From e4d36c1b7cbf8623ef5bae3d2228b76b89f2a80d Mon Sep 17 00:00:00 2001 From: subnixr Date: Sun, 26 Mar 2017 20:44:42 +0200 Subject: [PATCH 14/15] Add floating point support in cli.py --- README.md | 12 ++++++------ cli.py | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b795efd..54f2dd0 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,12 @@ python3 cli.py user add username password Add some products with: ``` -python3 cli.py product add "Birra media" 300 -python3 cli.py product add "Birra grande" 400 -python3 cli.py product add "Cocktail" 500 -python3 cli.py product add "Vino" 400 -python3 cli.py product add "Amaro" 200 -python3 cli.py product add "Acqua" 100 +python3 cli.py product add "Birra media" 3 +python3 cli.py product add "Birra grande" 4 +python3 cli.py product add "Cocktail" 5 +python3 cli.py product add "Vino" 4 +python3 cli.py product add "Amaro" 2 +python3 cli.py product add "Acqua" 0.5 ``` And finally add and event you can play with: diff --git a/cli.py b/cli.py index d7c6106..f7d9fb6 100755 --- a/cli.py +++ b/cli.py @@ -289,10 +289,11 @@ def tabulate_products(products): @product.command('add') @click.argument('name') -@click.argument('price') +@click.argument('price', type=float) @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) if sort: -- 2.30.2 From 545017bf2c9373d23aa1f70b2bd6fb7afe1d9627 Mon Sep 17 00:00:00 2001 From: subnixr Date: Sun, 26 Mar 2017 20:53:23 +0200 Subject: [PATCH 15/15] Clean little code --- static/js/sell.js | 54 ++++++++++++++++++++++++++++++++++++++++ templates/sell.html | 60 ++------------------------------------------- 2 files changed, 56 insertions(+), 58 deletions(-) create mode 100644 static/js/sell.js diff --git a/static/js/sell.js b/static/js/sell.js new file mode 100644 index 0000000..fda2ffe --- /dev/null +++ b/static/js/sell.js @@ -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 '' +} + +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)) +} diff --git a/templates/sell.html b/templates/sell.html index a87cf0f..8b8c66f 100644 --- a/templates/sell.html +++ b/templates/sell.html @@ -27,66 +27,10 @@ + {% endblock %} -- 2.30.2