Prepare 0.2.8 release

This commit is contained in:
s3lph 2021-04-07 09:36:18 +02:00
parent ea32a33cef
commit ddcdc0f816
25 changed files with 219 additions and 194 deletions

View file

@ -95,28 +95,6 @@ build_debian:
only:
- tags
build_archlinux:
stage: build
image: archlinux/base:latest # Use an archlinux image instead of the customized debian image.
script:
- pacman -Sy --noconfirm python python-setuptools python-pip python-wheel python-bottle python-jinja python-pillow python-magic base-devel
- export MATEMAT_VERSION=$(python -c 'import matemat; print(matemat.__version__)')
- cp -r static/ package/archlinux/matemat/usr/lib/matemat/static/
- cp -r templates/ package/archlinux/matemat/usr/lib/matemat/templates/
- python3 setup.py egg_info -d -b +master install --root=package/archlinux/matemat/ --prefix=/usr --optimize=1
- cd package/archlinux
- mv matemat/usr/bin/matemat matemat/usr/lib/matemat/matemat
- rm -rf matemat/usr/bin
- sed -re "s/__VERSION__/${MATEMAT_VERSION}/g" -i PKGBUILD
- sudo -u nobody makepkg -c
- sha256sum *.pkg.tar.xz > SHA256SUMS
artifacts:
paths:
- "package/archlinux/*.pkg.tar.xz"
- package/archlinux/SHA256SUMS
only:
- tags
release:
stage: deploy

View file

@ -1,5 +1,20 @@
# Matemat Changelog
<!-- BEGIN RELEASE v0.2.8 -->
## Version 0.2.8
Feature release
### Changes
<!-- BEGIN CHANGES 0.2.8 -->
- Feature: Add "custom price" products
- Fix: Buying not working when using the NullDispenser
- Breaking: Remove Arch Linux packaging
<!-- END CHANGES 0.2.8 -->
<!-- END RELEASE v0.2.8 -->
<!-- BEGIN RELEASE v0.2.7 -->
## Version 0.2.7

View file

@ -1,2 +1,2 @@
__version__ = '0.2.7'
__version__ = '0.2.8'

View file

@ -354,11 +354,11 @@ class MatematDatabase(object):
products: List[Product] = []
with self.db.transaction(exclusive=False) as c:
for row in c.execute('''
SELECT product_id, name, price_member, price_non_member, stock, stockable
SELECT product_id, name, price_member, price_non_member, custom_price, stock, stockable
FROM products
'''):
product_id, name, price_member, price_external, stock, stockable = row
products.append(Product(product_id, name, price_member, price_external, stockable, stock))
product_id, name, price_member, price_external, custom_price, stock, stockable = row
products.append(Product(product_id, name, price_member, price_external, custom_price, stockable, stock))
return products
def get_product(self, pid: int) -> Product:
@ -369,7 +369,7 @@ class MatematDatabase(object):
with self.db.transaction(exclusive=False) as c:
# Fetch all values to construct the product
c.execute('''
SELECT product_id, name, price_member, price_non_member, stock, stockable
SELECT product_id, name, price_member, price_non_member, custom_price, stock, stockable
FROM products
WHERE product_id = ?''',
[pid])
@ -377,15 +377,16 @@ class MatematDatabase(object):
if row is None:
raise ValueError(f'No product with product ID {pid} exists.')
# Unpack the row and construct the product
product_id, name, price_member, price_non_member, stock, stockable = row
return Product(product_id, name, price_member, price_non_member, stockable, stock)
product_id, name, price_member, price_non_member, custom_price, stock, stockable = row
return Product(product_id, name, price_member, price_non_member, custom_price, stockable, stock)
def create_product(self, name: str, price_member: int, price_non_member: int, stockable: bool) -> Product:
def create_product(self, name: str, price_member: int, price_non_member: int, custom_price: bool, stockable: bool) -> Product:
"""
Creates a new product.
:param name: Name of the product.
:param price_member: Price of the product for members.
:param price_non_member: Price of the product for non-members.
:param custom_price: Whether the price is customizable. If yes, the price values are understood as minimum.
:param stockable: True if the product should be stockable, false otherwise.
:return: A Product object representing the created product.
:raises ValueError: If a product with the same name already exists.
@ -396,17 +397,18 @@ class MatematDatabase(object):
if c.fetchone() is not None:
raise ValueError(f'A product with the name \'{name}\' already exists.')
c.execute('''
INSERT INTO products (name, price_member, price_non_member, stock, stockable)
VALUES (:name, :price_member, :price_non_member, 0, :stockable)
INSERT INTO products (name, price_member, price_non_member, custom_price, stock, stockable)
VALUES (:name, :price_member, :price_non_member, :custom_price, 0, :stockable)
''', {
'name': name,
'price_member': price_member,
'price_non_member': price_non_member,
'custom_price': custom_price,
'stockable': stockable
})
c.execute('SELECT last_insert_rowid()')
product_id = int(c.fetchone()[0])
return Product(product_id, name, price_member, price_non_member, stockable, 0)
return Product(product_id, name, price_member, price_non_member, custom_price, stockable, 0)
def change_product(self, product: Product, **kwargs) -> None:
"""
@ -422,6 +424,7 @@ class MatematDatabase(object):
name: str = kwargs['name'] if 'name' in kwargs else product.name
price_member: int = kwargs['price_member'] if 'price_member' in kwargs else product.price_member
price_non_member: int = kwargs['price_non_member'] if 'price_non_member' in kwargs else product.price_non_member
custom_price: int = kwargs['custom_price'] if 'custom_price' in kwargs else product.custom_price
stock: int = kwargs['stock'] if 'stock' in kwargs else product.stock
stockable: bool = kwargs['stockable'] if 'stockable' in kwargs else product.stockable
with self.db.transaction() as c:
@ -431,6 +434,7 @@ class MatematDatabase(object):
name = :name,
price_member = :price_member,
price_non_member = :price_non_member,
custom_price = :custom_price,
stock = :stock,
stockable = :stockable
WHERE product_id = :product_id
@ -439,6 +443,7 @@ class MatematDatabase(object):
'name': name,
'price_member': price_member,
'price_non_member': price_non_member,
'custom_price': custom_price,
'stock': stock,
'stockable': stockable
})
@ -450,6 +455,7 @@ class MatematDatabase(object):
product.name = name
product.price_member = price_member
product.price_non_member = price_non_member
product.custom_price = custom_price
product.stock = stock
product.stockable = stockable
@ -469,15 +475,18 @@ class MatematDatabase(object):
raise DatabaseConsistencyError(
f'delete_product should affect 1 products row, but affected {affected}')
def increment_consumption(self, user: User, product: Product) -> None:
def increment_consumption(self, user: User, product: Product, custom_price: int = None) -> None:
"""
Decrement the user's balance by the price of the product and create an entry in the statistics table.
:param user: The user buying a product.
:param product: The product the user is buying.
:param custom_price: The custom price chosen by the user.
:raises DatabaseConsistencyError: If the user or the product does not exist in the database.
"""
price: int = product.price_member if user.is_member else product.price_non_member
if product.custom_price and custom_price is not None:
price = max(price, custom_price)
with self.db.transaction() as c:
c.execute('''
INSERT INTO transactions (user_id, value, old_balance)

View file

@ -268,3 +268,11 @@ def migrate_schema_4_to_5(c: sqlite3.Cursor):
INSERT INTO products SELECT product_id, name, stock, 1, price_member, price_non_member FROM products_temp
''')
c.execute('DROP TABLE products_temp')
def migrate_schema_5_to_6(c: sqlite3.Cursor):
# Add custom_price column
c.execute('''
ALTER TABLE products ADD COLUMN
custom_price INTEGER(1) DEFAULT 0;
''')

View file

@ -8,17 +8,19 @@ class Product:
:param name: The product's name.
:param price_member: The price of a unit of this product for users marked as "members".
:param price_non_member: The price of a unit of this product for users NOT marked as "members".
:param custom_price: If true, the user can choose the price to pay, but at least the regular price.
:param stock: The number of items of this product currently in stock, or None if not stockable.
:param stockable: Whether this product is stockable.
"""
def __init__(self, _id: int, name: str,
price_member: int, price_non_member: int,
price_member: int, price_non_member: int, custom_price: bool,
stockable: bool, stock: int) -> None:
self.id: int = _id
self.name: str = name
self.price_member: int = price_member
self.price_non_member: int = price_non_member
self.custom_price: bool = custom_price
self.stock: int = stock
self.stockable: bool = stockable
@ -29,8 +31,9 @@ class Product:
self.name == other.name and \
self.price_member == other.price_member and \
self.price_non_member == other.price_non_member and \
self.custom_price == other.custom_price and \
self.stock == other.stock and \
self.stockable == other.stockable
def __hash__(self) -> int:
return hash((self.id, self.name, self.price_member, self.price_non_member, self.stock, self.stockable))
return hash((self.id, self.name, self.price_member, self.price_non_member, self.custom_price, self.stock, self.stockable))

View file

@ -333,3 +333,83 @@ SCHEMAS[5] = [
ON DELETE SET NULL ON UPDATE CASCADE
);
''']
SCHEMAS[6] = [
'''
CREATE TABLE users (
user_id INTEGER PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
email TEXT DEFAULT NULL,
password TEXT NOT NULL,
touchkey TEXT DEFAULT NULL,
is_admin INTEGER(1) NOT NULL DEFAULT 0,
is_member INTEGER(1) NOT NULL DEFAULT 1,
balance INTEGER(8) NOT NULL DEFAULT 0,
lastchange INTEGER(8) NOT NULL DEFAULT 0,
receipt_pref INTEGER(1) NOT NULL DEFAULT 0,
created INTEGER(8) NOT NULL DEFAULT 0
);
''',
'''
CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
stock INTEGER(8) DEFAULT 0,
stockable INTEGER(1) DEFAULT 1,
price_member INTEGER(8) NOT NULL,
price_non_member INTEGER(8) NOT NULL,
custom_price INTEGER(1) DEFAULT 0
);
''',
'''
CREATE TABLE transactions ( -- "superclass" of the following 3 tables
ta_id INTEGER PRIMARY KEY,
user_id INTEGER DEFAULT NULL,
value INTEGER(8) NOT NULL,
old_balance INTEGER(8) NOT NULL,
date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
FOREIGN KEY (user_id) REFERENCES users(user_id)
ON DELETE SET NULL ON UPDATE CASCADE
);
''',
'''
CREATE TABLE consumptions ( -- transactions involving buying a product
ta_id INTEGER PRIMARY KEY,
product TEXT NOT NULL,
FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
''',
'''
CREATE TABLE deposits ( -- transactions involving depositing cash
ta_id INTEGER PRIMARY KEY,
FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
''',
'''
CREATE TABLE modifications ( -- transactions involving balance modification by an admin
ta_id INTEGER NOT NULL,
agent TEXT NOT NULL,
reason TEXT DEFAULT NULL,
PRIMARY KEY (ta_id),
FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
''',
'''
CREATE TABLE receipts ( -- receipts sent to the users
receipt_id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
first_ta_id INTEGER DEFAULT NULL,
last_ta_id INTEGER DEFAULT NULL,
date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
FOREIGN KEY (user_id) REFERENCES users(user_id)
ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id)
ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id)
ON DELETE SET NULL ON UPDATE CASCADE
);
''']

View file

@ -220,7 +220,7 @@ class DatabaseTest(unittest.TestCase):
def test_create_product(self) -> None:
with self.db as db:
with db.transaction() as c:
db.create_product('Club Mate', 200, 200, True)
db.create_product('Club Mate', 200, 200, True, True)
c.execute("SELECT * FROM products")
row = c.fetchone()
self.assertEqual('Club Mate', row[1])
@ -228,13 +228,14 @@ class DatabaseTest(unittest.TestCase):
self.assertEqual(1, row[3])
self.assertEqual(200, row[4])
self.assertEqual(200, row[5])
self.assertEqual(1, row[6])
with self.assertRaises(ValueError):
db.create_product('Club Mate', 250, 250, False)
db.create_product('Club Mate', 250, 250, False, False)
def test_get_product(self) -> None:
with self.db as db:
with db.transaction(exclusive=False):
created = db.create_product('Club Mate', 150, 250, False)
created = db.create_product('Club Mate', 150, 250, False, False)
product = db.get_product(created.id)
self.assertEqual('Club Mate', product.name)
self.assertEqual(150, product.price_member)
@ -248,9 +249,9 @@ class DatabaseTest(unittest.TestCase):
# Test empty list
products = db.list_products()
self.assertEqual(0, len(products))
db.create_product('Club Mate', 200, 200, True)
db.create_product('Flora Power Mate', 200, 200, False)
db.create_product('Fritz Mate', 200, 250, True)
db.create_product('Club Mate', 200, 200, False, True)
db.create_product('Flora Power Mate', 200, 200, False, False)
db.create_product('Fritz Mate', 200, 250, False, True)
products = db.list_products()
self.assertEqual(3, len(products))
productcheck = {}
@ -272,13 +273,14 @@ class DatabaseTest(unittest.TestCase):
def test_change_product(self) -> None:
with self.db as db:
product = db.create_product('Club Mate', 200, 200, True)
product = db.create_product('Club Mate', 200, 200, False, True)
db.change_product(product, name='Flora Power Mate', price_member=150, price_non_member=250,
stock=None, stockable=False)
custom_price=True, stock=None, stockable=False)
# Changes must be reflected in the passed object
self.assertEqual('Flora Power Mate', product.name)
self.assertEqual(150, product.price_member)
self.assertEqual(250, product.price_non_member)
self.assertEqual(True, product.custom_price)
self.assertEqual(None, product.stock)
self.assertEqual(False, product.stockable)
# Changes must be reflected in the database
@ -286,11 +288,13 @@ class DatabaseTest(unittest.TestCase):
self.assertEqual('Flora Power Mate', checkproduct.name)
self.assertEqual(150, checkproduct.price_member)
self.assertEqual(250, checkproduct.price_non_member)
self.assertEqual(True, checkproduct.custom_price)
self.assertEqual(None, checkproduct.stock)
self.assertEqual(False, checkproduct.stockable)
product.id = -1
with self.assertRaises(DatabaseConsistencyError):
db.change_product(product)
product2 = db.create_product('Club Mate', 200, 200, True)
product2 = db.create_product('Club Mate', 200, 200, False, True)
product2.name = 'Flora Power Mate'
with self.assertRaises(DatabaseConsistencyError):
# Should fail, as a product with the same name already exists.
@ -298,8 +302,8 @@ class DatabaseTest(unittest.TestCase):
def test_delete_product(self) -> None:
with self.db as db:
product = db.create_product('Club Mate', 200, 200, True)
product2 = db.create_product('Flora Power Mate', 200, 200, False)
product = db.create_product('Club Mate', 200, 200, False, True)
product2 = db.create_product('Flora Power Mate', 200, 200, False, False)
self.assertEqual(2, len(db.list_products()))
db.delete_product(product)
@ -344,9 +348,9 @@ class DatabaseTest(unittest.TestCase):
db.deposit(user1, 1337)
db.deposit(user2, 4242)
db.deposit(user3, 1234)
clubmate = db.create_product('Club Mate', 200, 200, True)
florapowermate = db.create_product('Flora Power Mate', 150, 250, True)
fritzmate = db.create_product('Fritz Mate', 200, 200, True)
clubmate = db.create_product('Club Mate', 200, 200, False , True)
florapowermate = db.create_product('Flora Power Mate', 150, 250, False, True)
fritzmate = db.create_product('Fritz Mate', 200, 200, False, True)
# user1 is somewhat addicted to caffeine
for _ in range(3):
@ -472,7 +476,7 @@ class DatabaseTest(unittest.TestCase):
admin: User = db.create_user('admin', 'supersecurepassword', 'admin@example.com', True, True)
user: User = db.create_user('user', 'supersecurepassword', 'user@example.com', True, True)
product: Product = db.create_product('Flora Power Mate', 200, 200, True)
product: Product = db.create_product('Flora Power Mate', 200, 200, False, True)
# Create some transactions
db.change_user(user, agent=admin,
@ -561,8 +565,8 @@ class DatabaseTest(unittest.TestCase):
user2: User = db.create_user('user2', 'supersecurepassword', 'user2@example.com', True, False)
user3: User = db.create_user('user3', 'supersecurepassword', 'user3@example.com', True, False)
user4: User = db.create_user('user4', 'supersecurepassword', 'user4@example.com', True, False)
flora: Product = db.create_product('Flora Power Mate', 200, 200, True)
club: Product = db.create_product('Club Mate', 200, 200, False)
flora: Product = db.create_product('Flora Power Mate', 200, 200, False, True)
club: Product = db.create_product('Club Mate', 200, 200, False, False)
# Create some transactions
db.deposit(user1, 1337)

View file

@ -40,7 +40,7 @@ class DatabaseTransaction(object):
class DatabaseWrapper(object):
SCHEMA_VERSION = 5
SCHEMA_VERSION = 6
def __init__(self, filename: str) -> None:
self._filename: str = filename
@ -87,6 +87,8 @@ class DatabaseWrapper(object):
migrate_schema_3_to_4(c)
if from_version <= 4 and to_version >= 5:
migrate_schema_4_to_5(c)
if from_version <= 5 and to_version >= 6:
migrate_schema_5_to_6(c)
def connect(self) -> None:
if self.is_connected():

View file

@ -4,5 +4,4 @@ from matemat.interfacing.dispenser import Dispenser
class NullDispenser(Dispenser):
def dispense(self, product, amount):
# no-op
pass
return True

View file

@ -198,9 +198,10 @@ def handle_admin_change(args: FormsDict, files: FormsDict, db: MatematDatabase):
name = str(args.name)
price_member = parse_chf(str(args.pricemember))
price_non_member = parse_chf(str(args.pricenonmember))
custom_price = 'custom_price' in args
stockable = 'stockable' in args
# Create the user in the database
newproduct = db.create_product(name, price_member, price_non_member, stockable)
newproduct = db.create_product(name, price_member, price_non_member, custom_price, stockable)
# If a new product image was uploaded, process it
image = files.image.file.read() if 'image' in files else None
if image is not None and len(image) > 0:

View file

@ -26,8 +26,11 @@ def buy():
pid = int(str(request.params.pid))
product = db.get_product(pid)
if c.get_dispenser().dispense(product, 1):
price = None
if 'price' in request.params:
price = int(str(request.params.price))
# Create a consumption entry for the (user, product) combination
db.increment_consumption(user, product)
db.increment_consumption(user, product, price)
stock_provider = c.get_stock_provider()
if stock_provider.needs_update():
stock_provider.update_stock(product, -1)

View file

@ -93,13 +93,14 @@ def handle_change(args: FormsDict, files: FormsDict, product: Product, db: Matem
name = str(args.name)
price_member = parse_chf(str(args.pricemember))
price_non_member = parse_chf(str(args.pricenonmember))
custom_price = 'custom_price' in args
stock = int(str(args.stock))
stockable = 'stockable' in args
# Attempt to write the changes to the database
try:
db.change_product(product,
name=name, price_member=price_member, price_non_member=price_non_member,
stock=stock, stockable=stockable)
custom_price=custom_price, stock=stock, stockable=stockable)
stock_provider = get_stock_provider()
if stock_provider.needs_update() and product.stockable:
stock_provider.set_stock(product, stock)

View file

@ -1,30 +0,0 @@
# Maintainer: s3lph <account-gitlab-ideynizv@kernelpanic.lol>
pkgname=matemat
pkgver=0.2.4
pkgrel=1
arch=('any')
pkgdesc='A soda machine stock-keeping webservice'
url='https://gitlab.com/s3lph/matemat'
licence=('MIT')
depends=(
'python'
'python-bottle'
'python-jinja'
'python-pillow'
'python-magic'
'file'
)
backup=(
'etc/matemat.conf'
)
install=$pkgname.install
package() {
cp -r ../matemat/* ../pkg/matemat/
}

View file

@ -1,31 +0,0 @@
post_install() {
if ! getent group matemat >/dev/null; then
groupadd --system matemat
fi
if ! getent passwd matemat >/dev/null; then
useradd --system --create-home --gid matemat --home-dir /var/lib/matemat --shell /usr/bin/nologin matemat
fi
chown matemat:matemat /var/lib/matemat
chmod 0750 /var/lib/matemat
ln -sf /var/lib/matemat/upload /usr/lib/matemat/static/upload
systemctl daemon-reload
}
pre_remove() {
systemctl stop matemat.service
userdel matemat
}
post_remove() {
systemctl daemon-reload
}

View file

@ -1,32 +0,0 @@
[Matemat]
# The IP address to listen on
Address=::
# The TCP port to listen on
Port=80
# The log level, one of NONE, DEBUG, INFO, WARNING, ERROR, CRITICAL
LogLevel=DEBUG
[Pagelets]
# Name of the Matemat instance, shown in the web app
InstanceName=Matemat
#
# Configure SMTP credentials
#
# SmtpFrom=matemat@example.com
# SmtpSubj=Matemat Receipt
# SmtpHost=exmaple.com
# SmtpPort=587
# SmtpUser=matemat@example.com
# SmtpPass=supersecurepassword
#
# Enable to allow users to receive receipts via email
#
# SmtpSendReceipts=1
# Add static HTTP headers in this section
# [HttpHeaders]

View file

@ -1,11 +0,0 @@
[Matemat]
StaticPath=/usr/lib/matemat/static
TemplatePath=/usr/lib/matemat/templates
LogTarget=stdout
[Pagelets]
UploadDir=/var/lib/matemat/upload
DatabaseFile=/var/lib/matemat/matemat.db

View file

@ -1,12 +0,0 @@
[Unit]
Description=matemat
After=networking.target
[Service]
ExecStart=/usr/bin/python -m matemat /etc/matemat.conf /usr/lib/matemat/matemat.conf
User=matemat
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target

View file

@ -1,5 +1,5 @@
Package: matemat
Version: 0.2.7
Version: 0.2.8
Maintainer: s3lph <account-gitlab-ideynizv@kernelpanic.lol>
Section: web
Priority: optional

View file

@ -84,14 +84,6 @@ def fetch_debian_url(base_url: str, job_ids: Dict[str, str]) -> Optional[Tuple[s
return debian_url, debian_sha_url
def fetch_arch_url(base_url: str, job_ids: Dict[str, str]) -> Optional[Tuple[str, str]]:
mybase: str = f'{base_url}/jobs/{job_ids["build_archlinux"]}/artifacts/raw'
arch_sha_url: str = f'{mybase}/package/archlinux/SHA256SUMS'
arch_filename: str = fetch_single_shafile(arch_sha_url)
arch_url: str = f'{mybase}/package/archlinux/{arch_filename}'
return arch_url, arch_sha_url
def main():
api_token: Optional[str] = os.getenv('GITLAB_API_TOKEN')
release_tag: Optional[str] = os.getenv('CI_COMMIT_TAG')
@ -125,7 +117,6 @@ def main():
wheel_url, wheel_sha_url = fetch_wheel_url(base_url, job_ids)
debian_url, debian_sha_url = fetch_debian_url(base_url, job_ids)
arch_url, arch_sha_url = fetch_arch_url(base_url, job_ids)
augmented_changelog = f'''{changelog.strip()}
@ -133,7 +124,6 @@ def main():
- [Python Wheel]({wheel_url}) ([sha256]({wheel_sha_url}))
- [Debian Package]({debian_url}) ([sha256]({debian_sha_url}))
- [Arch Linux Package]({arch_url}) ([sha256]({arch_sha_url}))
- Docker image: registry.gitlab.com/{project_name}:{release_tag}'''
post_body: str = json.dumps({'description': augmented_changelog})

View file

@ -91,18 +91,32 @@
color: blue;
}
#deposit-amount {
#deposit-output {
grid-column-start: 1;
grid-column-end: 4;
text-align: right;
grid-row: 1;
font-size: 50px;
line-heigt: 100px;
padding: 10px 0;
font-family: monospace;
background: #ffffff;
}
#deposit-title {
display: block;
font-family: sans-serif;
padding: 0 10px;
line-height: 30px;
font-size: 25px;
height: 30px;
}
#deposit-amount {
display: block;
text-align: right;
font-size: 50px;
line-height: 70px;
height: 70px;
padding: 0 10px;
font-family: monospace;
}
.numpad {
background: #f0f0f0;
text-decoration: none;

View file

@ -4,25 +4,42 @@ Number.prototype.pad = function(size) {
return s;
}
let product_id = null;
let deposit = '0';
let button = document.createElement('div');
let input = document.getElementById('deposit-wrapper');
let amount = document.getElementById('deposit-amount');
let title = document.getElementById('deposit-title');
button.classList.add('thumblist-item');
button.classList.add('fakelink');
button.innerText = 'Deposit';
button.onclick = (ev) => {
product_id = null;
deposit = '0';
title.innerText = 'Deposit';
amount.innerText = (Math.floor(parseInt(deposit) / 100)) + '.' + (parseInt(deposit) % 100).pad();
input.classList.add('show');
};
setup_custom_price = (pid, pname) => {
product_id = pid;
title.innerText = pname;
deposit = '0';
amount.innerText = (Math.floor(parseInt(deposit) / 100)) + '.' + (parseInt(deposit) % 100).pad();
input.classList.add('show');
};
deposit_key = (k) => {
if (k == 'ok') {
window.location.href = '/deposit?n=' + parseInt(deposit);
if (product_id === null) {
window.location.href = '/deposit?n=' + parseInt(deposit);
} else {
window.location.href = '/buy?pid=' + product_id + '&price=' + parseInt(deposit);
}
deposit = '0';
product_id = null;
input.classList.remove('show');
} else if (k == 'del') {
if (deposit == '0') {
product_id = null;
input.classList.remove('show');
}
deposit = deposit.substr(0, deposit.length - 1);

View file

@ -49,6 +49,9 @@
<label for="admin-newproduct-price-non-member">Non-member price: </label>
CHF <input id="admin-newproduct-price-non-member" type="number" step="0.01" name="pricenonmember" value="0" /><br/>
<label for="admin-custom-price"><abbr title="When 'Custom Price' is enabled, users choose the price to pay, but at least the prices given above">Custom Price</abbr>: </label>
<input id="admin-custom-price" type="checkbox" name="custom_price" /><br/>
<label for="admin-newproduct-stockable">Stockable: </label>
<input id="admin-newproduct-stockable" type="checkbox" name="stockable" checked="checked" /><br/>

View file

@ -20,6 +20,9 @@
<label for="modproduct-price-non-member">Non-member price: </label>
CHF <input id="modproduct-price-non-member" type="number" step="0.01" name="pricenonmember" value="{{ product.price_non_member|chf(False) }}" /><br/>
<label for="modproduct-custom-price"><abbr title="When 'Custom Price' is enabled, users choose the price to pay, but at least the prices given above">Custom Price</abbr>: </label>
<input id="modproduct-custom-price" type="checkbox" name="custom_price" {% if product.custom_price %} checked="checked" {% endif %} /><br/>
<label for="modproduct-stockable">Stockable: </label>
<input id="modproduct-stockable" type="checkbox" name="stockable" {% if product.stockable %} checked="checked" {% endif %} /><br/>

View file

@ -26,7 +26,10 @@
</div>
<div id="deposit-wrapper">
<div id="deposit-input">
<span id="deposit-amount">0.00</span>
<div id="deposit-output">
<span id="deposit-title"></span>
<span id="deposit-amount">0.00</span>
</div>
{% for i in [('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8'), ('9', '9'), ('del', '✗'), ('0', '0'), ('ok', '✓')] %}
<div class="numpad" id="numpad-{{ i.0 }}" onclick="deposit_key('{{ i.0 }}');">{{ i.1 }}</div>
{% endfor %}
@ -38,8 +41,15 @@
{% for product in products %}
{# Show an item per product, consisting of the name, image, price and stock, triggering a purchase on click #}
<div class="thumblist-item">
{% if product.custom_price %}
<a onclick="setup_custom_price({{ product.id }}, '{{ product.name}}');">
{% else %}
<a href="/buy?pid={{ product.id }}">
{% endif %}
<span class="thumblist-title">{{ product.name }}</span>
{% if product.custom_price %}
<span class="thumblist-detail">Custom Price</span><br/>
{% else %}
<span class="thumblist-detail">Price:
{% if authuser.is_member %}
{{ product.price_member|chf }}
@ -47,6 +57,7 @@
{{ product.price_non_member|chf }}
{% endif %}
</span><br/>
{% endif %}
<div class="imgcontainer">
<img src="/static/upload/thumbnails/products/{{ product.id }}.png" alt="Picture of {{ product.name }}"/>
{% set pstock = stock.get_stock(product) %}