diff --git a/app/migrations/versions/55013fe95bea_create_price_modified_column.py b/app/migrations/versions/55013fe95bea_create_price_modified_column.py
new file mode 100644
index 0000000..d4010c1
--- /dev/null
+++ b/app/migrations/versions/55013fe95bea_create_price_modified_column.py
@@ -0,0 +1,21 @@
+"""Create price_modified column
+
+Revision ID: 55013fe95bea
+Revises: 9159a6fed021
+Create Date: 2022-04-22 01:00:03.729596
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '55013fe95bea'
+down_revision = '9159a6fed021'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+ op.add_column('order_item', sa.Column('price_modified', sa.DateTime(), nullable=True))
+
+
+def downgrade():
+ op.drop_column('order_item', 'price_modified')
diff --git a/app/models/orderitem.py b/app/models/orderitem.py
index 6e85e6c..623d751 100644
--- a/app/models/orderitem.py
+++ b/app/models/orderitem.py
@@ -18,6 +18,7 @@ class OrderItem(db.Model):
dish_id = db.Column(db.String(64), nullable=True)
dish_name = db.Column(db.String(120), nullable=True)
price = db.Column(db.Integer, nullable=True)
+ price_modified = db.Column(db.DateTime, nullable=True)
paid = db.Column(db.Boolean, default=False, nullable=True)
comment = db.Column(db.Text(), nullable=True)
hlds_data_version = db.Column(db.String(40), nullable=True)
diff --git a/app/templates/order.html b/app/templates/order.html
index 60e1cf5..02fdccd 100644
--- a/app/templates/order.html
+++ b/app/templates/order.html
@@ -307,7 +307,12 @@
{%- endif %}
-
{{ item.price|euro }}
+
+ {{ item.price|euro }}
+ {% if item.price_modified %}
+
+ {% endif %}
+
{{ item.dish_name }}{{ "; " + item.comment if item.comment }}
{% endfor %}
@@ -328,6 +333,14 @@
+
+ {% if order.is_closed() %}
+
+
+ Edit prices
+
+ {% endif %}
+
diff --git a/app/templates/order_prices.html b/app/templates/order_prices.html
new file mode 100644
index 0000000..bbb9ca5
--- /dev/null
+++ b/app/templates/order_prices.html
@@ -0,0 +1,130 @@
+{% extends "layout.html" %}
+{% set active_page = "orders" -%}
+
+{% import "utils.html" as util %}
+
+{% block metas %}
+ {{ super() }}
+
+{% endblock %}
+
+{% block container %}
+
+
+
+
+{% endblock %}
+
+{% block styles %}
+ {{ super() }}
+
+{% endblock %}
+
+{% block scripts %}
+ {{ super() }}
+
+{% endblock %}
diff --git a/app/utils.py b/app/utils.py
index 91c9793..80556f2 100644
--- a/app/utils.py
+++ b/app/utils.py
@@ -1,16 +1,25 @@
"Script which contains several utils for Haldis"
-from typing import Iterable
+import re
+from typing import Iterable, Optional
-def euro_string(value: int) -> str:
+def euro_string(value: int, unit="€ ") -> str:
"""
Convert cents to string formatted euro
"""
euro, cents = divmod(value, 100)
if cents:
- return f"€ {euro}.{cents:02}"
- return f"€ {euro}"
+ return f"{unit}{euro}.{cents:02}"
+ return f"{unit}{euro}"
+
+
+def parse_euro_string(value: str) -> Optional[int]:
+ m = re.fullmatch("(?:€ ?)?([0-9]+)(?:[.,]([0-9]+))?", value)
+ if not m:
+ return None
+ cents_02 = "{:0<2.2}".format(m.group(2)) if m.group(2) else "00"
+ return int(m.group(1)) * 100 + int(cents_02)
def price_range_string(price_range, include_upper=False):
diff --git a/app/views/order.py b/app/views/order.py
index ad2ccad..cd3847a 100644
--- a/app/views/order.py
+++ b/app/views/order.py
@@ -1,5 +1,6 @@
"Script to generate the order related views of Haldis"
import random
+import re
import typing
from datetime import datetime
@@ -11,7 +12,7 @@ from forms import AnonOrderItemForm, OrderForm, OrderItemForm
from hlds.definitions import location_definition_version, location_definitions
from models import Order, OrderItem, User, db
from notification import post_order_to_webhook
-from utils import ignore_none
+from utils import ignore_none, parse_euro_string
from werkzeug.wrappers import Response
order_bp = Blueprint("order_bp", "order")
@@ -323,6 +324,54 @@ def close_order(order_id: int) -> typing.Optional[Response]:
return None
+@order_bp.route("//prices", methods=["GET", "POST"])
+@login_required
+def prices(order_id: int) -> typing.Optional[Response]:
+ order = Order.query.filter(Order.id == order_id).first()
+ if order is None:
+ abort(404)
+ if (
+ current_user.is_anonymous() or
+ not (current_user.is_admin() or current_user.id == order.courier_id)
+ ):
+ flash("Only the courier can edit prices.", "error")
+ return redirect(url_for("order_bp.order_from_id", order_id=order_id))
+ if not order.is_closed():
+ flash("Cannot modify prices until the order is closed.", "error")
+ return redirect(url_for("order_bp.order_from_id", order_id=order_id))
+
+ if request.method == "GET":
+ return render_template(
+ "order_prices.html",
+ order=order,
+ )
+ else:
+ new_prices = {}
+
+ for key, value in request.form.items():
+ m = re.fullmatch("item_([0-9]+)", key)
+ if not m:
+ continue
+ item_id = int(m.group(1))
+
+ price = parse_euro_string(value)
+ if not price:
+ flash(f"Could not recognize '{value}' as a price")
+ continue
+
+ new_prices[item_id] = price
+
+ for item in order.items:
+ new_price = new_prices.get(item.id)
+ if new_price is not None and new_price != item.price:
+ item.price = new_price
+ item.price_modified = datetime.now()
+ db.session.commit()
+
+ return redirect(url_for("order_bp.order_from_id", order_id=order_id))
+
+
+
def select_user(items) -> typing.Optional[User]:
"Select a random user from those who are signed up for the order"
user = None