impossible to order above max_limit for items

This commit is contained in:
Laetitia Getti 2023-05-26 17:03:10 +02:00
parent 2087b49fc4
commit a14267368c
5 changed files with 211 additions and 22 deletions

View file

@ -31,6 +31,11 @@ class GroupedOrder(models.Model):
self.total_price = price self.total_price = price
self.save() self.save()
def compute_items_ordered_nb(self):
"""Updates the ordered_nb of all items fo the grouped_order"""
for item in self.item_set.all():
item.compute_ordered_nb()
def is_ongoing(self): def is_ongoing(self):
"""Returns True if the grouped order is open for new Orders - False if it's too late""" """Returns True if the grouped order is open for new Orders - False if it's too late"""
return self.deadline >= timezone.now() return self.deadline >= timezone.now()
@ -130,6 +135,13 @@ class Item(models.Model):
"""Returns the price of all orders on this item""" """Returns the price of all orders on this item"""
return self.price * self.ordered_nb return self.price * self.ordered_nb
def get_remaining_nb(self):
"""Returns the number of remaining articles for this item in this grouped order"""
if self.max_limit:
return self.max_limit - self.ordered_nb
else:
return None
def get_absolute_url(self): def get_absolute_url(self):
return reverse("order:manage_items", kwargs={"pk": self.grouped_order.pk}) return reverse("order:manage_items", kwargs={"pk": self.grouped_order.pk})

View file

@ -26,7 +26,7 @@
<tr> <tr>
<form method="post" action="{{ create_item_url }}"> <form method="post" action="{{ create_item_url }}">
{% csrf_token %} {% csrf_token %}
<td><input name="name" maxlength="100" placeholder="Nom du produit" required></input></td> <td><input name="name" maxlength="100" placeholder="Nom du produit" autofocus required></input></td>
<td><input name="price" size="2" placeholder="5,40" required></input></td> <td><input name="price" size="2" placeholder="5,40" required></input></td>
<td><input name="max_limit" size="2" placeholder="42"></input></td> <td><input name="max_limit" size="2" placeholder="42"></input></td>
<td><button type="submit" class="button is-primary">Ajouter</button></td> <td><button type="submit" class="button is-primary">Ajouter</button></td>

View file

@ -64,7 +64,8 @@
{% csrf_token %} {% csrf_token %}
<td>{{ item.name }}</td> <td>{{ item.name }}</td>
<td>{{ item.price }} €</td> <td>{{ item.price }} €</td>
<td><input name="quantity_{{ item.id }}" size="2" type="number" value="0" min="0"></input></td> <td><input name="quantity_{{ item.id }}" size="2" type="number" value="0" min="0"></input>
{% if item.get_remaining_nb %}<span class="is-italic"> {{ item.get_remaining_nb }} disponibles</span>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -232,7 +232,7 @@ class TestGroupedOrderDetailView:
orga_user=other_user, orga_user=other_user,
) )
item = models.Item.objects.create( item = models.Item.objects.create(
name="test item 1", grouped_order=grouped_order, price=1 name="test item 1", grouped_order=grouped_order, price=1, max_limit=20
) )
item2 = models.Item.objects.create( item2 = models.Item.objects.create(
name="test item 2", grouped_order=grouped_order, price=5 name="test item 2", grouped_order=grouped_order, price=5
@ -247,6 +247,7 @@ class TestGroupedOrderDetailView:
assert response.status_code == 200 assert response.status_code == 200
assert "test item" in response.content.decode() assert "test item" in response.content.decode()
assert "gr order test" in response.content.decode() assert "gr order test" in response.content.decode()
assert "20 disponibles" in response.content.decode()
assert item.ordered_nb == 0 assert item.ordered_nb == 0
assert item2.ordered_nb == 0 assert item2.ordered_nb == 0
order_url = reverse( order_url = reverse(
@ -286,11 +287,13 @@ class TestGroupedOrderDetailView:
assert order.ordered_items.count() == 2 assert order.ordered_items.count() == 2
assert order.articles_nb == 5 assert order.articles_nb == 5
assert order.price == 9 assert order.price == 9
assert item.get_remaining_nb() == 16
def test_order_item__no_articles_ordered(self, client, other_user): def test_order_item__no_articles_ordered(self, client, other_user):
""" """
From the OrderDetailView, we order without having changed any item quantity. From the OrderDetailView, we order without having changed any item quantity.
An error is raised. An error is raised.
The order is deleted
""" """
grouped_order = create_grouped_order( grouped_order = create_grouped_order(
days_before_delivery_date=5, days_before_delivery_date=5,
@ -328,13 +331,13 @@ class TestGroupedOrderDetailView:
"email": "test@mail.fr", "email": "test@mail.fr",
}, },
) )
order = models.Order.objects.first()
order.articles_nb == 0
assert response.status_code == 200 assert response.status_code == 200
assert ( assert (
response.context["error_message"] response.context["error_message"]
== "Veuillez commander au moins un produit" == "Veuillez commander au moins un produit"
) )
assert not models.Order.objects.first()
assert not models.OrderAuthor.objects.first()
def test_deadline_passed(self, client, other_user): def test_deadline_passed(self, client, other_user):
""" """
@ -577,6 +580,122 @@ class TestOrder:
) )
assert response.status_code == 403 assert response.status_code == 403
def test_order_too_many_items_ordered(self, client, other_user):
"""If a user orderd more articles than what is available,
the form is displayed again with an error.
The OrderedItems, OrderAuthor and Order are deleted."""
grouped_order = create_grouped_order(
days_before_delivery_date=5,
days_before_deadline=2,
name="gr order test",
orga_user=other_user,
)
item = models.Item.objects.create(
name="test item 1", grouped_order=grouped_order, price=1
)
item2 = models.Item.objects.create(
name="test item 2", grouped_order=grouped_order, price=5, max_limit=20
)
detail_url = reverse(
"order:grouped_order_detail",
kwargs={
"pk": grouped_order.pk,
},
)
response = client.get(detail_url)
assert response.status_code == 200
assert "test item" in response.content.decode()
assert "gr order test" in response.content.decode()
assert item.ordered_nb == 0
assert item2.ordered_nb == 0
order_url = reverse(
"order:order",
kwargs={
"grouped_order_id": grouped_order.pk,
},
)
response = client.post(
order_url,
{
f"quantity_{item.pk}": 4,
f"quantity_{item2.pk}": 25,
"first_name": "Prénom",
"last_name": "Nom",
"phone": "0645632569",
"email": "test@mail.fr",
},
)
assert response.status_code == 200
assert (
response.context["error_message"]
== "Trop de test item 2 commandés pour la quantité disponible"
)
item.refresh_from_db()
item2.refresh_from_db()
assert item.ordered_nb == 0
assert item2.ordered_nb == 0
assert not models.OrderAuthor.objects.first()
assert not models.Order.objects.first()
assert not models.OrderedItem.objects.first()
def test_negative_nb_ordered(self, client, other_user):
"""If a user orders a negative nb of articles for an item,
the form is displayed again with an error.
The OrderedItems, OrderAuthor and Order are deleted."""
grouped_order = create_grouped_order(
days_before_delivery_date=5,
days_before_deadline=2,
name="gr order test",
orga_user=other_user,
)
item = models.Item.objects.create(
name="test item 1", grouped_order=grouped_order, price=1
)
item2 = models.Item.objects.create(
name="test item 2", grouped_order=grouped_order, price=5, max_limit=20
)
detail_url = reverse(
"order:grouped_order_detail",
kwargs={
"pk": grouped_order.pk,
},
)
response = client.get(detail_url)
assert response.status_code == 200
assert "test item" in response.content.decode()
assert "gr order test" in response.content.decode()
assert item.ordered_nb == 0
assert item2.ordered_nb == 0
order_url = reverse(
"order:order",
kwargs={
"grouped_order_id": grouped_order.pk,
},
)
response = client.post(
order_url,
{
f"quantity_{item.pk}": 4,
f"quantity_{item2.pk}": -2,
"first_name": "Prénom",
"last_name": "Nom",
"phone": "0645632569",
"email": "test@mail.fr",
},
)
assert response.status_code == 200
assert (
response.context["error_message"]
== "Veuillez commander un nombre positif de test item 2"
)
item.refresh_from_db()
item2.refresh_from_db()
assert item.ordered_nb == 0
assert item2.ordered_nb == 0
assert not models.OrderAuthor.objects.first()
assert not models.Order.objects.first()
assert not models.OrderedItem.objects.first()
class TestGroupedOrderCreateView: class TestGroupedOrderCreateView:
def test_create_grouped_order(self, client_log): def test_create_grouped_order(self, client_log):

View file

@ -2,6 +2,7 @@ from io import BytesIO
from django import http from django import http
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import ValidationError
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.template.loader import get_template from django.template.loader import get_template
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
@ -62,6 +63,14 @@ class GroupedOrderDetailView(generic.DetailView):
template_name = "order/grouped_order_detail.html" template_name = "order/grouped_order_detail.html"
context_object_name = "grouped_order" context_object_name = "grouped_order"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
qty_dict = {}
for item in self.get_object().item_set.all():
qty_dict[item.id] = item.get_remaining_nb()
context["remaining_qty"] = qty_dict
return context
class GroupedOrderOverview(UserPassesTestMixin, generic.DetailView): class GroupedOrderOverview(UserPassesTestMixin, generic.DetailView):
"""Overview of a grouped order, for the organizer""" """Overview of a grouped order, for the organizer"""
@ -144,12 +153,17 @@ class ItemCreateView(UserPassesTestMixin, generic.CreateView):
def order(request, grouped_order_id): def order(request, grouped_order_id):
"""Creates an AnonymousUser, and an Order for this GroupedOrder, with related OrderedItems""" """Creates an AnonymousUser, and an Order for this GroupedOrder, with related OrderedItems"""
grouped_order = get_object_or_404(GroupedOrder, pk=grouped_order_id) grouped_order = get_object_or_404(GroupedOrder, pk=grouped_order_id)
# check if the grouped order is ongoing
if not grouped_order.is_ongoing(): if not grouped_order.is_ongoing():
return http.HttpResponseForbidden() return http.HttpResponseForbidden()
# get a dict with (quantity_{{item_id}}:{{quantity}}) # get a dict with (quantity_{{item_id}}:{{quantity}})
orders_dict = { orders_dict = {
key: value for key, value in request.POST.items() if key.startswith("quantity") key: value for key, value in request.POST.items() if key.startswith("quantity")
} }
# create an order
first_name = request.POST["first_name"] first_name = request.POST["first_name"]
last_name = request.POST["last_name"] last_name = request.POST["last_name"]
phone = request.POST["phone"] phone = request.POST["phone"]
@ -158,33 +172,76 @@ def order(request, grouped_order_id):
first_name=first_name, last_name=last_name, email=email, phone=phone first_name=first_name, last_name=last_name, email=email, phone=phone
) )
order = Order.objects.create(author=author, grouped_order=grouped_order) order = Order.objects.create(author=author, grouped_order=grouped_order)
# add items to the order
error_message = None
for key, quantity in orders_dict.items(): for key, quantity in orders_dict.items():
if quantity == "": quantity = int(quantity)
quantity = 0 item = grouped_order.item_set.get(pk=key.split("_")[1])
if int(quantity) > 0: # check if too many items are ordered
item = grouped_order.item_set.get(pk=key.split("_")[1]) error_message = validate_item_ordered_nb(item, quantity)
OrderedItem.objects.create(nb=quantity, order=order, item=item) if error_message:
item.compute_ordered_nb() break # stop creating items if there is an error
order.compute_order_articles_nb() OrderedItem.objects.create(nb=quantity, order=order, item=item)
if order.articles_nb == 0:
# Redisplay the order form for this grouped order. # Redisplay the form with error messages if there is an error
if error_message:
order.delete()
author.delete()
grouped_order.compute_items_ordered_nb()
return render( return render(
request, request,
"order/grouped_order_detail.html", "order/grouped_order_detail.html",
{ {
"grouped_order": grouped_order, "grouped_order": grouped_order,
"error_message": "Veuillez commander au moins un produit", "error_message": error_message,
}, },
) )
else:
order.compute_order_price() # check if the order contains articles
# Always return an http.HttpResponseRedirect after successfully dealing error_message = validate_articles_ordered_nb(order)
# with POST data. This prevents data from being posted twice if a
# user hits the Back button. # Redisplay the form with error messages if there is an error
return http.HttpResponseRedirect( if error_message:
reverse("order:order_confirm", args=(grouped_order.pk, order.pk)) order.delete()
author.delete()
return render(
request,
"order/grouped_order_detail.html",
{
"grouped_order": grouped_order,
"error_message": error_message,
},
) )
# Redirect to confirmation page
order.compute_order_price()
grouped_order.compute_items_ordered_nb()
# Always return an http.HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return http.HttpResponseRedirect(
reverse("order:order_confirm", args=(grouped_order.pk, order.pk))
)
def validate_item_ordered_nb(item, ordered_nb):
"""Returns an error message if the ordered items are not available
or if the ordered nb is negative"""
if ordered_nb < 0:
return f"Veuillez commander un nombre positif de {item.name}"
if item.get_remaining_nb() and item.get_remaining_nb() - ordered_nb < 0:
return f"Trop de {item.name} commandés pour la quantité disponible"
return None
def validate_articles_ordered_nb(order):
"""Return an error if no items are ordered"""
order.compute_order_articles_nb()
if order.articles_nb == 0:
return "Veuillez commander au moins un produit"
return None
class OrderDetailView(generic.DetailView): class OrderDetailView(generic.DetailView):
"""Confirmation page after a user orders""" """Confirmation page after a user orders"""