Make creating order and adding items work
This commit is contained in:
parent
0e7675cec6
commit
6f24b52855
10 changed files with 110 additions and 76 deletions
16
app/forms.py
16
app/forms.py
|
@ -7,10 +7,10 @@ from flask_wtf import FlaskForm as Form
|
|||
from wtforms import (DateTimeField, SelectField, StringField, SubmitField,
|
||||
validators)
|
||||
|
||||
from utils import euro_string
|
||||
from hlds.definitions import location_definitions
|
||||
from hlds.models import Location
|
||||
from models import User
|
||||
from utils import euro_string
|
||||
|
||||
|
||||
class OrderForm(Form):
|
||||
|
@ -18,7 +18,7 @@ class OrderForm(Form):
|
|||
# pylint: disable=R0903
|
||||
courier_id = SelectField("Courier", coerce=int)
|
||||
location_id = SelectField(
|
||||
"Location", coerce=int, validators=[validators.required()]
|
||||
"Location", coerce=str, validators=[validators.required()]
|
||||
)
|
||||
starttime = DateTimeField(
|
||||
"Starttime", default=datetime.now, format="%d-%m-%Y %H:%M"
|
||||
|
@ -47,15 +47,15 @@ class OrderForm(Form):
|
|||
class OrderItemForm(Form):
|
||||
"Class which defines the form for a new Item in an Order"
|
||||
# pylint: disable=R0903
|
||||
product_id = SelectField("Item", coerce=int)
|
||||
extra = StringField("Extra")
|
||||
dish_id = SelectField("Dish")
|
||||
comment = StringField("Comment")
|
||||
submit_button = SubmitField("Submit")
|
||||
|
||||
def populate(self, location: Location) -> None:
|
||||
"Fill in all the product options from the location"
|
||||
self.product_id.choices = [
|
||||
"Fill in all the dish options from the location"
|
||||
self.dish_id.choices = [
|
||||
(i.id, (i.name + ": " + euro_string(i.price)))
|
||||
for i in location.products
|
||||
for i in location.dishes
|
||||
]
|
||||
|
||||
|
||||
|
@ -68,7 +68,7 @@ class AnonOrderItemForm(OrderItemForm):
|
|||
|
||||
def populate(self, location: Location) -> None:
|
||||
"""
|
||||
Fill in all the product options from the location and
|
||||
Fill in all the dish options from the location and
|
||||
the name of the anon user
|
||||
"""
|
||||
OrderItemForm.populate(self, location)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# pylint: disable=too-few-public-methods
|
||||
|
||||
from typing import Iterable, List, Mapping, Any, Optional
|
||||
from utils import euro_string
|
||||
|
||||
|
||||
def _format_tags(tags: Iterable[str]) -> str:
|
||||
|
@ -13,7 +14,7 @@ def _format_tags(tags: Iterable[str]) -> str:
|
|||
|
||||
|
||||
def _format_price(price: int) -> str:
|
||||
return " € {}.{:02}".format(*divmod(price, 100)) if price else ""
|
||||
return " {}".format(euro_string(price)) if price else ""
|
||||
|
||||
|
||||
def _format_type_and_choice(type_and_choice):
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import typing
|
||||
from datetime import datetime
|
||||
|
||||
from utils import first
|
||||
from hlds.definitions import location_definitions
|
||||
from .database import db
|
||||
from .user import User
|
||||
|
||||
|
@ -18,13 +20,12 @@ class Order(db.Model):
|
|||
|
||||
items = db.relationship("OrderItem", backref="order", lazy="dynamic")
|
||||
|
||||
def configure(self, courier: User,
|
||||
starttime: db.DateTime, stoptime: db.DateTime,) -> None:
|
||||
"Configure the Order"
|
||||
# pylint: disable=W0201
|
||||
self.courier = courier
|
||||
self.starttime = starttime
|
||||
self.stoptime = stoptime
|
||||
def __getattr__(self, name):
|
||||
if name == "location":
|
||||
location = first(filter(lambda l: l.id == self.location_id, location_definitions))
|
||||
if location:
|
||||
return location
|
||||
raise AttributeError()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# pylint: disable=R1705
|
||||
|
@ -33,6 +34,14 @@ class Order(db.Model):
|
|||
else:
|
||||
return "Order %d" % (self.id)
|
||||
|
||||
def update_from_hlds(self) -> None:
|
||||
"""
|
||||
Update the location name from the HLDS definition.
|
||||
User should commit after running this to make the change persistent.
|
||||
"""
|
||||
assert self.location_id, "location_id must be configured before updating from HLDS"
|
||||
self.location_name = self.location.name
|
||||
|
||||
def group_by_user(self) -> typing.Dict[str, typing.Any]:
|
||||
"Group items of an Order by user"
|
||||
group: typing.Dict[str, typing.Any] = dict()
|
||||
|
@ -44,20 +53,20 @@ class Order(db.Model):
|
|||
item.price if not item.paid else 0
|
||||
)
|
||||
user["paid"] = user.get("paid", True) and item.paid
|
||||
user["products"] = user.get("products", []) + [item.dish_name]
|
||||
user["dishes"] = user.get("dishes", []) + [item.dish_name]
|
||||
group[item.get_name()] = user
|
||||
|
||||
return group
|
||||
|
||||
def group_by_product(self) -> typing.Dict[str, typing.Any]:
|
||||
"Group items of an Order by product"
|
||||
def group_by_dish(self) -> typing.Dict[str, typing.Any]:
|
||||
"Group items of an Order by dish"
|
||||
group: typing.Dict[str, typing.Any] = dict()
|
||||
for item in self.items:
|
||||
product = group.get(item.dish_name, dict())
|
||||
product["count"] = product.get("count", 0) + 1
|
||||
if item.extra:
|
||||
product["extras"] = product.get("extras", []) + [item.comment]
|
||||
group[item.dish_name] = product
|
||||
dish = group.get(item.dish_name, dict())
|
||||
dish["count"] = dish.get("count", 0) + 1
|
||||
if item.comment:
|
||||
dish["comments"] = dish.get("comments", []) + [item.comment]
|
||||
group[item.dish_name] = dish
|
||||
|
||||
return group
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"Script for everything OrderItem related in the database"
|
||||
from datetime import datetime
|
||||
|
||||
from utils import first
|
||||
from hlds.definitions import location_definitions
|
||||
from .database import db
|
||||
from .order import Order
|
||||
from .user import User
|
||||
|
@ -23,17 +25,21 @@ class OrderItem(db.Model):
|
|||
|
||||
choices = db.relationship("OrderItemChoice", backref="order_item", lazy="dynamic")
|
||||
|
||||
def configure(self, user: User, order: Order) -> None:
|
||||
"Configure the OrderItem"
|
||||
# pylint: disable=W0201
|
||||
self.user = user
|
||||
self.order = order
|
||||
def __getattr__(self, name):
|
||||
if name == "dish":
|
||||
location_id = Order.query.filter(Order.id == self.order_id).first().location_id
|
||||
location = first(filter(lambda l: l.id == location_id, location_definitions))
|
||||
if location:
|
||||
return first(filter(lambda d: d.id == self.dish_id, location.dishes))
|
||||
else:
|
||||
raise ValueError("No Location found with id: " + location_id)
|
||||
raise AttributeError()
|
||||
|
||||
def get_name(self) -> str:
|
||||
"Get the name of the user which 'owns' the item"
|
||||
if self.user_id is not None and self.user_id > 0:
|
||||
return self.user.username
|
||||
return self.name
|
||||
return self.user_name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Order %d: %s wants %s" % (
|
||||
|
@ -42,6 +48,16 @@ class OrderItem(db.Model):
|
|||
self.dish_name or "None",
|
||||
)
|
||||
|
||||
def update_from_hlds(self) -> None:
|
||||
"""
|
||||
Update the dish name and price from the HLDS definition.
|
||||
User should commit after running this to make the change persistent.
|
||||
"""
|
||||
assert self.order_id, "order_id must be configured before updating from HLDS"
|
||||
assert self.dish_id, "dish_id must be configured before updating from HLDS"
|
||||
self.dish_name = self.dish.name
|
||||
self.price = self.dish.price
|
||||
|
||||
# pylint: disable=W0613
|
||||
def can_delete(self, order_id: int, user_id: int, name: str) -> bool:
|
||||
"Check if a user can delete an item"
|
||||
|
|
|
@ -10,30 +10,30 @@ from flask import url_for
|
|||
from models.order import Order
|
||||
|
||||
|
||||
def webhook_text(order_item: Order) -> typing.Optional[str]:
|
||||
def webhook_text(order: Order) -> typing.Optional[str]:
|
||||
"Function that makes the text for the notification"
|
||||
if "Testlocation" in order_item.location.name:
|
||||
if order.location_id == "test":
|
||||
return None
|
||||
|
||||
if order_item.courier is not None:
|
||||
if order.courier is not None:
|
||||
# pylint: disable=C0301
|
||||
return "<!channel|@channel> {3} is going to {1}, order <{0}|here>! Deadline in {2} minutes!".format(
|
||||
url_for("order_bp.order_from_id", order_id=order_item.id, _external=True),
|
||||
order_item.location.name,
|
||||
remaining_minutes(order_item.stoptime),
|
||||
order_item.courier.username.title(),
|
||||
url_for("order_bp.order_from_id", order_id=order.id, _external=True),
|
||||
order.location_name,
|
||||
remaining_minutes(order.stoptime),
|
||||
order.courier.username.title(),
|
||||
)
|
||||
|
||||
return "<!channel|@channel> New order for {}. Deadline in {} minutes. <{}|Open here.>".format(
|
||||
order_item.location.name,
|
||||
remaining_minutes(order_item.stoptime),
|
||||
url_for("order_bp.order_from_id", order_id=order_item.id, _external=True),
|
||||
order.location_name,
|
||||
remaining_minutes(order.stoptime),
|
||||
url_for("order_bp.order_from_id", order_id=order.id, _external=True),
|
||||
)
|
||||
|
||||
|
||||
def post_order_to_webhook(order_item: Order) -> None:
|
||||
def post_order_to_webhook(order: Order) -> None:
|
||||
"Function that sends the notification for the order"
|
||||
message = webhook_text(order_item)
|
||||
message = webhook_text(order)
|
||||
if message:
|
||||
webhookthread = WebhookSenderThread(
|
||||
message, app.config["SLACK_WEBHOOK"])
|
||||
|
|
|
@ -43,15 +43,15 @@
|
|||
<a class="btn btn-primary" onclick="chooseRandom()">Choose for me</a>
|
||||
</span>
|
||||
{{ form.csrf_token }}
|
||||
<div class="form-group select2-container select2 {{ 'has-errors' if form.product_id.errors}}">
|
||||
{{ form.product_id.label(class='control-label') }}<br>
|
||||
{{ form.product_id(class='form-control select') }}
|
||||
{{ util.render_form_field_errors(form.product_id) }}
|
||||
<div class="form-group select2-container select2 {{ 'has-errors' if form.dish_id.errors}}">
|
||||
{{ form.dish_id.label(class='control-label') }}<br>
|
||||
{{ form.dish_id(class='form-control select') }}
|
||||
{{ util.render_form_field_errors(form.dish_id) }}
|
||||
</div>
|
||||
<div class="form-group {{ 'has-errors' if form.product_id.errors }}">
|
||||
{{ form.extra.label(class='control-label') }}<br>
|
||||
{{ form.extra(class='form-control', placeholder='Fill in extras, when applicable') }}
|
||||
{{ util.render_form_field_errors(form.extra) }}
|
||||
<div class="form-group {{ 'has-errors' if form.dish_id.errors }}">
|
||||
{{ form.comment.label(class='control-label') }}<br>
|
||||
{{ form.comment(class='form-control', placeholder='Fill in comment, when applicable') }}
|
||||
{{ util.render_form_field_errors(form.comment) }}
|
||||
</div>
|
||||
{% if current_user.is_anonymous() %}
|
||||
<div class="form-group{{ ' has-error' if form.name.errors }}{{ ' required' if form.name.flags.required }}">
|
||||
|
@ -78,8 +78,8 @@
|
|||
{% for item in order.items -%}
|
||||
<tr>
|
||||
<td>{{ item.get_name() }}</td>
|
||||
<td><span title="{{ item.extra if item.extra }}">{{ item.product.name }}{{ "*" if item.extra }}</span></td>
|
||||
<td>{{ item.product.price|euro }}</td>
|
||||
<td><span title="{{ item.comment if item.comment }}">{{ item.dish_name }}{{ "*" if item.comment }}</span></td>
|
||||
<td>{{ item.price|euro }}</td>
|
||||
{% if courier_or_admin %}<td>{% if not item.paid %}
|
||||
<form action="{{ url_for('order_bp.item_paid', order_id=order.id, item_id=item.id) }}" method="post" style="display:inline">
|
||||
<input type="submit" class="btn btn-xs btn-primary" value="Pay"></input>
|
||||
|
@ -98,15 +98,15 @@
|
|||
</table>
|
||||
</div>
|
||||
<div class="col-md-push-2 col-md-4 darker box order_ordered" id="items-ordered">
|
||||
<h3>Ordered products: {{ order.items.count() }}</h3>
|
||||
<h3>Ordered dishes: {{ order.items.count() }}</h3>
|
||||
<a class="divLink" href="{{ url_for('order_bp.items_showcase', order_id=order.id) }}"></a>
|
||||
{% for key, value in order.group_by_product().items() -%}
|
||||
{% for key, value in order.group_by_dish().items() -%}
|
||||
<div class="product">
|
||||
{{ key }}: {{ value["count"] }}
|
||||
{% if value["extras"] -%}
|
||||
<div class="extras">
|
||||
{% for extra in value["extras"] -%}
|
||||
<div>- {{ extra }}</div>
|
||||
{% if value["comments"] -%}
|
||||
<div class="comments">
|
||||
{% for comment in value["comments"] -%}
|
||||
<div>- {{ comment }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{%- endif %}
|
||||
|
|
|
@ -22,8 +22,8 @@ Haldis - Order {{ order.id }}
|
|||
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-sm-offset-3 darker showcase" id="items-ordered">
|
||||
<h3 class="text-center">Ordered products: {{ order.items.count() }}</h3>
|
||||
{% for key, value in order.group_by_product().items() -%}
|
||||
<h3 class="text-center">Ordered dishes: {{ order.items.count() }}</h3>
|
||||
{% for key, value in order.group_by_dish().items() -%}
|
||||
<div class="product text-center">
|
||||
<p>
|
||||
{{ key }}: {{ value["count"] }}
|
||||
|
|
16
app/utils.py
16
app/utils.py
|
@ -1,9 +1,21 @@
|
|||
"Script which contains several utils for Haldis"
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
def euro_string(value: int) -> str:
|
||||
"""
|
||||
Convert cents to string formatted euro
|
||||
"""
|
||||
result = "€ {:.2f}".format(round(value / 100, 2))
|
||||
return result
|
||||
return "€ {}.{:02}".format(*divmod(value, 100))
|
||||
|
||||
|
||||
def first(iterable: Iterable, default=None):
|
||||
"""
|
||||
Return first element of iterable
|
||||
"""
|
||||
try:
|
||||
return next(iter(iterable))
|
||||
except StopIteration:
|
||||
return default
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ from flask import current_app as app
|
|||
from flask import send_from_directory, url_for
|
||||
from flask_login import login_required
|
||||
|
||||
from utils import first
|
||||
from hlds.definitions import location_definitions
|
||||
from hlds.models import Location
|
||||
from models import Order
|
||||
|
@ -21,13 +22,6 @@ from views.order import get_orders
|
|||
general_bp = Blueprint("general_bp", __name__)
|
||||
|
||||
|
||||
def _first(iterable: typing.Iterable, default=None):
|
||||
try:
|
||||
return next(iter(iterable))
|
||||
except StopIteration:
|
||||
return default
|
||||
|
||||
|
||||
@general_bp.route("/")
|
||||
def home() -> str:
|
||||
"Generate the home view"
|
||||
|
@ -131,7 +125,7 @@ def locations() -> str:
|
|||
@general_bp.route("/location/<location_id>")
|
||||
def location(location_id) -> str:
|
||||
"Generate the location view given an id"
|
||||
loc = _first(filter(lambda l: l.id == location_id, location_definitions))
|
||||
loc = first(filter(lambda l: l.id == location_id, location_definitions))
|
||||
if loc is None:
|
||||
abort(404)
|
||||
return render_template("location.html", location=loc, title=loc.name)
|
||||
|
|
|
@ -37,6 +37,7 @@ def order_create() -> typing.Union[str, Response]:
|
|||
if orderForm.validate_on_submit():
|
||||
order = Order()
|
||||
orderForm.populate_obj(order)
|
||||
order.update_from_hlds()
|
||||
db.session.add(order)
|
||||
db.session.commit()
|
||||
post_order_to_webhook(order)
|
||||
|
@ -59,8 +60,8 @@ def order_from_id(order_id: int, form: OrderForm = None) -> str:
|
|||
form.populate(order.location)
|
||||
if order.stoptime and order.stoptime < datetime.now():
|
||||
form = None
|
||||
total_price = sum([o.product.price for o in order.items])
|
||||
debts = sum([o.product.price for o in order.items if not o.paid])
|
||||
total_price = sum([o.price for o in order.items])
|
||||
debts = sum([o.price for o in order.items if not o.paid])
|
||||
return render_template("order.html", order=order, form=form,
|
||||
total_price=total_price, debts=debts)
|
||||
|
||||
|
@ -121,9 +122,10 @@ def order_item_create(order_id: int) -> typing.Any:
|
|||
item.user_id = current_user.id
|
||||
else:
|
||||
session["anon_name"] = item.name
|
||||
item.update_from_hlds()
|
||||
db.session.add(item)
|
||||
db.session.commit()
|
||||
flash("Ordered %s" % (item.product.name), "success")
|
||||
flash("Ordered %s" % (item.dish_name), "success")
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
return order_from_id(order_id, form=form)
|
||||
|
||||
|
@ -138,7 +140,7 @@ def item_paid(order_id: int, item_id: int) -> typing.Optional[Response]:
|
|||
if item.order.courier_id == user_id or current_user.admin:
|
||||
item.paid = True
|
||||
db.session.commit()
|
||||
flash("Paid %s by %s" % (item.product.name, item.get_name()),
|
||||
flash("Paid %s by %s" % (item.dish_name, item.get_name()),
|
||||
"success")
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
abort(404)
|
||||
|
@ -184,10 +186,10 @@ def delete_item(order_id: int, item_id: int) -> typing.Any:
|
|||
print("%s tries to delete orders" % (current_user.username))
|
||||
user_id = current_user.id
|
||||
if item.can_delete(order_id, user_id, session.get("anon_name", "")):
|
||||
product_name = item.product.name
|
||||
dish_name = item.dish_name
|
||||
db.session.delete(item)
|
||||
db.session.commit()
|
||||
flash("Deleted %s" % (product_name), "success")
|
||||
flash("Deleted %s" % (dish_name), "success")
|
||||
return redirect(url_for("order_bp.order_from_id", order_id=order_id))
|
||||
abort(404)
|
||||
|
||||
|
|
Loading…
Reference in a new issue