diff --git a/pyproject.toml b/pyproject.toml index a729fcb8..3aef89a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ sentry-sdk = "^1.14.0" [tool.black] line-length = 119 -target-version = ["py310"] +target-version = ["py311"] exclude = ''' /( migrations @@ -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" diff --git a/website/borrel/urls.py b/website/borrel/urls.py index ca7d64ed..116e80dc 100644 --- a/website/borrel/urls.py +++ b/website/borrel/urls.py @@ -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 = [ diff --git a/website/orders/api/v1/filters.py b/website/orders/api/v1/filters.py index 4d92cfd1..ca02cec6 100644 --- a/website/orders/api/v1/filters.py +++ b/website/orders/api/v1/filters.py @@ -54,6 +54,7 @@ class Meta: ), "ready": ("exact",), "paid": ("exact",), + "picked_up": ("exact",), "type": ("exact",), "product": ("exact",), } diff --git a/website/orders/api/v1/serializers.py b/website/orders/api/v1/serializers.py index e00b5354..c9e3f7fc 100644 --- a/website/orders/api/v1/serializers.py +++ b/website/orders/api/v1/serializers.py @@ -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): diff --git a/website/orders/api/v1/views.py b/website/orders/api/v1/views.py index 863220f2..5046aaea 100644 --- a/website/orders/api/v1/views.py +++ b/website/orders/api/v1/views.py @@ -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): @@ -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.") @@ -107,6 +106,7 @@ class OrderRetrieveUpdateDestroyAPIView(LoggedRetrieveUpdateDestroyAPIView): "properties": { "ready": {"type": "boolean"}, "paid": {"type": "boolean"}, + "picked_up": {"type": "boolean"}, "priority": {"type": "number"}, }, } diff --git a/website/orders/migrations/0003_order_picked_up_order_picked_up_at.py b/website/orders/migrations/0003_order_picked_up_order_picked_up_at.py new file mode 100644 index 00000000..6a5eb9a5 --- /dev/null +++ b/website/orders/migrations/0003_order_picked_up_order_picked_up_at.py @@ -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), + ), + ] diff --git a/website/orders/models.py b/website/orders/models.py index 8bb84dd5..8f5f1831 100644 --- a/website/orders/models.py +++ b/website/orders/models.py @@ -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."}) @@ -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) @@ -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 diff --git a/website/orders/services.py b/website/orders/services.py index 37fdf0b5..acf21bfa 100644 --- a/website/orders/services.py +++ b/website/orders/services.py @@ -56,7 +56,7 @@ 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). @@ -64,6 +64,7 @@ def add_scanned_order(product: Product, shift: Shift, ready=True, paid=True) -> :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 @@ -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, ) @@ -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: """ @@ -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 @@ -146,6 +156,7 @@ def add_user_order( user_association=user.association, paid=paid, ready=ready, + picked_up=picked_up, priority=priority, ) diff --git a/website/orders/templates/orders/order_admin_list.html b/website/orders/templates/orders/order_admin_list.html index d70fb6d7..e007fda6 100644 --- a/website/orders/templates/orders/order_admin_list.html +++ b/website/orders/templates/orders/order_admin_list.html @@ -220,6 +220,20 @@
${ index + 1 }$.
${ order.product.name }$ (€${ (Math.round(order.order_price * 100) / 100).toFixed(2) }$)