Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add start of status screen and add new picked up button #520

Merged
merged 5 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ sentry-sdk = "^1.14.0"

[tool.black]
line-length = 119
target-version = ["py310"]
target-version = ["py311"]
exclude = '''
/(
migrations
Expand All @@ -62,5 +62,5 @@ exclude = '''
'''

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
5 changes: 1 addition & 4 deletions website/borrel/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from django.urls import path, register_converter
from django.urls import path

from borrel import views
from venues.converters import VenueConverter

register_converter(VenueConverter, "venue")


urlpatterns = [
Expand Down
1 change: 1 addition & 0 deletions website/orders/api/v1/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class Meta:
),
"ready": ("exact",),
"paid": ("exact",),
"picked_up": ("exact",),
"type": ("exact",),
"product": ("exact",),
}
14 changes: 13 additions & 1 deletion website/orders/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,22 @@ class Meta:
"ready_at",
"paid",
"paid_at",
"picked_up",
"picked_up_at",
"type",
"priority",
]
read_only_fields = ["id", "created", "user", "product", "order_price", "ready_at", "paid_at"]
read_only_fields = [
"id",
"created",
"user",
"product",
"order_price",
"ready_at",
"paid_at",
"picked_up_at",
"prioritize",
]


class ShiftSerializer(WritableModelSerializer):
Expand Down
8 changes: 4 additions & 4 deletions website/orders/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ class OrderListCreateAPIView(ListCreateAPIView):
"GET": ["orders:order"],
"POST": ["orders:manage"],
}
filter_backends = [
django_filters.rest_framework.DjangoFilterBackend,
]
filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter]
filterset_class = OrderFilter
ordering_fields = ["paid_at", "ready_at", "picked_up_at"]
queryset = Order.objects.select_related("user", "product")

def get_queryset(self):
Expand All @@ -72,7 +71,7 @@ def perform_create(self, serializer):
# Save the order while ignoring the order_type, user, paid and ready argument as the user does not have
# permissions to save orders for all users in the shift.
order = serializer.save(
shift=shift, type=Order.TYPE_ORDERED, user=self.request.user, paid=False, ready=False
shift=shift, type=Order.TYPE_ORDERED, user=self.request.user, paid=False, ready=False, picked_up=False
)
log_action(self.request.user, order, CHANGE, "Created order via API.")

Expand Down Expand Up @@ -107,6 +106,7 @@ class OrderRetrieveUpdateDestroyAPIView(LoggedRetrieveUpdateDestroyAPIView):
"properties": {
"ready": {"type": "boolean"},
"paid": {"type": "boolean"},
"picked_up": {"type": "boolean"},
"priority": {"type": "number"},
},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.0.4 on 2024-04-28 19:53

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("orders", "0002_initial"),
]

operations = [
migrations.AddField(
model_name="order",
name="picked_up",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="order",
name="picked_up_at",
field=models.DateTimeField(blank=True, null=True),
),
]
19 changes: 17 additions & 2 deletions website/orders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,9 @@ def _clean(self):
elif old_instance is not None and not old_instance.finalized and self.finalized:
# Shift was not finalized yet but will be made finalized now
if not self.shift_done:
raise ValidationError({"finalized": "Shift can't be finalized if not all Orders are paid and ready"})
raise ValidationError(
{"finalized": "Shift can't be finalized if not all Orders are paid, ready and picked up."}
)

if self.end <= self.start:
raise ValidationError({"end": "End date cannot be before start date."})
Expand Down Expand Up @@ -545,6 +547,9 @@ class Order(models.Model):
paid = models.BooleanField(default=False)
paid_at = models.DateTimeField(null=True, blank=True)

picked_up = models.BooleanField(default=False)
picked_up_at = models.DateTimeField(null=True, blank=True)

type = models.PositiveIntegerField(choices=TYPES, default=TYPE_ORDERED)

priority = models.PositiveIntegerField(choices=PRIORITIES, default=PRIORITY_NORMAL)
Expand Down Expand Up @@ -587,12 +592,22 @@ def venue(self):
"""
return self.shift.venue

@property
def completed(self) -> bool:
"""
Check if an Order is completed.

:return: True if this Order is paid, ready and picked up, False otherwise.
:rtype: boolean
"""
return self.paid and self.ready and self.picked_up

@property
def done(self):
"""
Check if an Order is done.

:return: True if this Order is paid and ready, False otherwise
:return: True if this Order is paid, ready and picked up, False otherwise
:rtype: boolean
"""
return self.paid and self.ready
Expand Down
15 changes: 13 additions & 2 deletions website/orders/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ def execute_data_minimisation(dry_run=False):
return users


def add_scanned_order(product: Product, shift: Shift, ready=True, paid=True) -> Order:
def add_scanned_order(product: Product, shift: Shift, ready=True, paid=True, picked_up=True) -> Order:
"""
Add a single Scanned Order (of type TYPE_SCANNED).

:param product: A Product for which an Order has to be created
:param shift: The shift for which the Orders have to be created
:param ready: Whether the Order should be directly made ready
:param paid: Whether the Order should be directly made paid
:param picked_up: Whether the Order should be directly made picked up
:return: The created Order
"""
# Check if Shift is not finalized
Expand All @@ -79,7 +80,14 @@ def add_scanned_order(product: Product, shift: Shift, ready=True, paid=True) ->
raise OrderException("This Product is not available in this Shift")

return Order.objects.create(
product=product, shift=shift, type=Order.TYPE_SCANNED, user=None, user_association=None, ready=ready, paid=paid
product=product,
shift=shift,
type=Order.TYPE_SCANNED,
user=None,
user_association=None,
ready=ready,
paid=paid,
picked_up=picked_up,
)


Expand All @@ -90,6 +98,7 @@ def add_user_order(
priority: int = Order.PRIORITY_NORMAL,
paid: bool = False,
ready: bool = False,
picked_up: bool = False,
**kwargs,
) -> Order:
"""
Expand All @@ -101,6 +110,7 @@ def add_user_order(
:param priority: Which priority the Order should have
:param paid: Whether the order should be set as paid
:param ready: Whether the order should be set as ready
:param picked_up: Whether the order should be set as picked up
:return: The created Order
"""
# Check order permissions
Expand Down Expand Up @@ -146,6 +156,7 @@ def add_user_order(
user_association=user.association,
paid=paid,
ready=ready,
picked_up=picked_up,
priority=priority,
)

Expand Down
44 changes: 42 additions & 2 deletions website/orders/templates/orders/order_admin_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,20 @@ <h3 class="mt-3">Orders total</h3>
});
return orders_ready;
},
orders_picked_up() {
return this.orders.filter(order => order.picked_up);
},
orders_picked_up_grouped() {
let orders_picked_up = {};
this.orders_picked_up.forEach(order => {
if (order.product.name in orders_picked_up) {
orders_picked_up[order.product.name].amount += 1;
} else {
orders_picked_up[order.product.name] = {"product": order.product, "amount": 1};
}
});
return orders_picked_up;
},
orders_grouped() {
let orders_total = {};
this.orders.forEach(order => {
Expand All @@ -232,10 +246,10 @@ <h3 class="mt-3">Orders total</h3>
return orders_total;
},
orders_finished() {
return this.orders.filter(order => order.ready && order.paid && order.type !== 1);
return this.orders.filter(order => order.ready && order.paid && order.picked_up && order.type !== 1);
},
orders_to_process() {
return this.orders.filter(order => !order.ready || !order.paid && order.type !== 1);
return this.orders.filter(order => !order.ready || !order.picked_up || !order.paid && order.type !== 1);
},
orders_scanned() {
return this.orders.filter(order => order.type === 1);
Expand Down Expand Up @@ -378,6 +392,32 @@ <h3 class="mt-3">Orders total</h3>
}
}).catch(error => show_error_from_api(error));
},
toggle_picked_up(order) {
fetch(
`/api/v1/shifts/{{ shift.id }}/orders/${order.id}/`,
{
method: 'PATCH',
headers: {
"X-CSRFToken": get_csrf_token(),
"Accept": 'application/json',
"Content-Type": 'application/json',
},
body: JSON.stringify({
picked_up: !order.picked_up
})
}
).then(response => {
if (response.status === 200) {
return response;
} else {
throw response;
}
}).then(() => {
if (typeof (update_refresh_list) !== 'undefined') {
update_refresh_list();
}
}).catch(error => show_error_from_api(error));
},
delete_order(order) {
if (window.confirm('Do you want to delete this order?')) {
fetch(
Expand Down
51 changes: 29 additions & 22 deletions website/orders/templates/orders/order_admin_order.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
<ul class="row w-100">
<div class="col-auto col-lg-4 d-inline-flex">
<div class="col d-inline-flex">
<p class="item-counter">${ index + 1 }$.</p>
<p class="item-name">${ order.product.name }$ (€${ (Math.round(order.order_price * 100) /
100).toFixed(2) }$)</p>
<i v-if="order.product.icon !== null" :class="`fa-solid fa-${order.product.icon} item-icon`"></i>
</div>
<div class="col col-lg-5 d-inline-flex" style="min-width: 0">
<div class="ms-auto"></div>
<p v-if="order.user !== null" class="text-truncate" style="min-width: 0">
<i v-if="order.user !== null && {{ user.id }} === order.user.id"
class="fa-solid fa-user fa-xs"></i>
${ order.user.first_name }$ ${ order.user.last_name }$
</p>
<i v-if="order.user !== null && {{ user.id }} === order.user.id && order.deprioritize === false"
class="fa-solid fa-arrow-down-short-wide" style="font-size: 1.2em; cursor: pointer;" v-on:click="order_to_bottom_of_list(order)"></i>
<p><a target="_blank" :href="`{% url 'admin:orders_order_changelist' %}${order.id}/change/`"><i class="fa-solid fa-pen-to-square fa-xs text-white"></i></a></p>
</div>
<div v-if="!shift.finalized" class="col-12 col-lg-3 d-inline-flex gap-2">
<button v-if="order.paid" type="button" class="checkbox-paid btn col btn-success"
v-on:click="toggle_paid(order)">Paid <i class="fa-regular fa-circle-check"></i></button>
<button v-else type="button" class="checkbox-paid btn col btn-danger"
v-on:click="toggle_paid(order)">Paid <i class="fa-regular fa-circle-xmark"></i></button>

<button v-if="order.ready" type="button" class="checkbox-ready btn col btn-success"
v-on:click="toggle_ready(order)">Ready <i class="fa-regular fa-circle-check"></i></button>
<button v-else type="button" class="checkbox-ready btn col btn-danger"
v-on:click="toggle_ready(order)">Ready <i class="fa-regular fa-circle-xmark"></i></button>
<div class="col-lg-5">
<div class="row">
<div class="col d-inline-flex">
<div class="ms-auto"></div>
<p v-if="order.user !== null" class="text-truncate" style="min-width: 0">
<i v-if="order.user !== null && {{ user.id }} === order.user.id"
class="fa-solid fa-user fa-xs"></i>
${ order.user.first_name }$ ${ order.user.last_name }$
</p>
<p><a target="_blank" :href="`{% url 'admin:orders_order_changelist' %}${order.id}/change/`"><i class="fa-solid fa-pen-to-square fa-xs text-white"></i></a></p>
</div>
</div>
<div v-if="!shift.finalized" class="row">
<div class="col d-inline-flex gap-2">
<button v-if="order.paid" type="button" class="checkbox-paid btn col btn-success"
v-on:click="toggle_paid(order)">Paid <i class="fa-regular fa-circle-check"></i></button>
<button v-else type="button" class="checkbox-paid btn col btn-danger"
v-on:click="toggle_paid(order)">Paid <i class="fa-regular fa-circle-xmark"></i></button>
<button v-if="order.ready" type="button" class="checkbox-ready btn col btn-success"
v-on:click="toggle_ready(order)">Ready <i class="fa-regular fa-circle-check"></i></button>
<button v-else type="button" class="checkbox-ready btn col btn-danger"
v-on:click="toggle_ready(order)">Ready <i class="fa-regular fa-circle-xmark"></i></button>
<button v-if="order.picked_up" type="button" class="checkbox-picked-up btn col btn-success"
v-on:click="toggle_picked_up(order)">Picked up <i class="fa-regular fa-circle-check"></i></button>
<button v-else type="button" class="checkbox-picked-up btn col btn-danger"
v-on:click="toggle_picked_up(order)">Picked up <i class="fa-regular fa-circle-xmark"></i></button>
</div>
</div>
</div>
</ul>
Empty file.
8 changes: 8 additions & 0 deletions website/status_screen/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.apps import AppConfig


class StatusScreenConfig(AppConfig):
"""Status Screen App Config."""

default_auto_field = "django.db.models.BigAutoField"
name = "status_screen"
Empty file.
43 changes: 43 additions & 0 deletions website/status_screen/static/status_screen/css/status-screen.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.site-header {
height: 84px;
}

.user-order-list {
font-size: 30pt;
list-style-type: none;
padding-inline-start: unset;
padding: unset;
display: flex;
justify-content: center;
align-content: flex-start;
gap: 10px;
flex-wrap: wrap;
}

.user-order-list li.user-order-item {
display: inline-flex;
background-color: var(--primary);
border-radius: 15px;
padding: 10px 15px;
justify-content: center;
}

.user-order-list li .order-user-name {
margin-right: 20px;
}

.order-list {
list-style-type: none;
padding-inline-start: unset;
padding: unset;
}

.order-list li.order-item {
display: inline;
}

@media screen and (max-width: 992px) {
.site-header {
height: 59px;
}
}
Loading
Loading