mirror of
https://framagit.org/la-chariotte/la-chariotte.git
synced 2025-05-02 11:52:27 +02:00
impossible to order above max_limit for items
This commit is contained in:
parent
2087b49fc4
commit
a14267368c
5 changed files with 211 additions and 22 deletions
|
@ -31,6 +31,11 @@ class GroupedOrder(models.Model):
|
|||
self.total_price = price
|
||||
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):
|
||||
"""Returns True if the grouped order is open for new Orders - False if it's too late"""
|
||||
return self.deadline >= timezone.now()
|
||||
|
@ -130,6 +135,13 @@ class Item(models.Model):
|
|||
"""Returns the price of all orders on this item"""
|
||||
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):
|
||||
return reverse("order:manage_items", kwargs={"pk": self.grouped_order.pk})
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<tr>
|
||||
<form method="post" action="{{ create_item_url }}">
|
||||
{% 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="max_limit" size="2" placeholder="42"></input></td>
|
||||
<td><button type="submit" class="button is-primary">Ajouter</button></td>
|
||||
|
|
|
@ -64,7 +64,8 @@
|
|||
{% csrf_token %}
|
||||
<td>{{ item.name }}</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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -232,7 +232,7 @@ class TestGroupedOrderDetailView:
|
|||
orga_user=other_user,
|
||||
)
|
||||
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(
|
||||
name="test item 2", grouped_order=grouped_order, price=5
|
||||
|
@ -247,6 +247,7 @@ class TestGroupedOrderDetailView:
|
|||
assert response.status_code == 200
|
||||
assert "test item" 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 item2.ordered_nb == 0
|
||||
order_url = reverse(
|
||||
|
@ -286,11 +287,13 @@ class TestGroupedOrderDetailView:
|
|||
assert order.ordered_items.count() == 2
|
||||
assert order.articles_nb == 5
|
||||
assert order.price == 9
|
||||
assert item.get_remaining_nb() == 16
|
||||
|
||||
def test_order_item__no_articles_ordered(self, client, other_user):
|
||||
"""
|
||||
From the OrderDetailView, we order without having changed any item quantity.
|
||||
An error is raised.
|
||||
The order is deleted
|
||||
"""
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
|
@ -328,13 +331,13 @@ class TestGroupedOrderDetailView:
|
|||
"email": "test@mail.fr",
|
||||
},
|
||||
)
|
||||
order = models.Order.objects.first()
|
||||
order.articles_nb == 0
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
response.context["error_message"]
|
||||
== "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):
|
||||
"""
|
||||
|
@ -577,6 +580,122 @@ class TestOrder:
|
|||
)
|
||||
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:
|
||||
def test_create_grouped_order(self, client_log):
|
||||
|
|
|
@ -2,6 +2,7 @@ from io import BytesIO
|
|||
|
||||
from django import http
|
||||
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.template.loader import get_template
|
||||
from django.urls import reverse, reverse_lazy
|
||||
|
@ -62,6 +63,14 @@ class GroupedOrderDetailView(generic.DetailView):
|
|||
template_name = "order/grouped_order_detail.html"
|
||||
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):
|
||||
"""Overview of a grouped order, for the organizer"""
|
||||
|
@ -144,12 +153,17 @@ class ItemCreateView(UserPassesTestMixin, generic.CreateView):
|
|||
def order(request, grouped_order_id):
|
||||
"""Creates an AnonymousUser, and an Order for this GroupedOrder, with related OrderedItems"""
|
||||
grouped_order = get_object_or_404(GroupedOrder, pk=grouped_order_id)
|
||||
|
||||
# check if the grouped order is ongoing
|
||||
if not grouped_order.is_ongoing():
|
||||
return http.HttpResponseForbidden()
|
||||
|
||||
# get a dict with (quantity_{{item_id}}:{{quantity}})
|
||||
orders_dict = {
|
||||
key: value for key, value in request.POST.items() if key.startswith("quantity")
|
||||
}
|
||||
|
||||
# create an order
|
||||
first_name = request.POST["first_name"]
|
||||
last_name = request.POST["last_name"]
|
||||
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
|
||||
)
|
||||
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():
|
||||
if quantity == "":
|
||||
quantity = 0
|
||||
if int(quantity) > 0:
|
||||
item = grouped_order.item_set.get(pk=key.split("_")[1])
|
||||
OrderedItem.objects.create(nb=quantity, order=order, item=item)
|
||||
item.compute_ordered_nb()
|
||||
order.compute_order_articles_nb()
|
||||
if order.articles_nb == 0:
|
||||
# Redisplay the order form for this grouped order.
|
||||
quantity = int(quantity)
|
||||
item = grouped_order.item_set.get(pk=key.split("_")[1])
|
||||
# check if too many items are ordered
|
||||
error_message = validate_item_ordered_nb(item, quantity)
|
||||
if error_message:
|
||||
break # stop creating items if there is an error
|
||||
OrderedItem.objects.create(nb=quantity, order=order, item=item)
|
||||
|
||||
# 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(
|
||||
request,
|
||||
"order/grouped_order_detail.html",
|
||||
{
|
||||
"grouped_order": grouped_order,
|
||||
"error_message": "Veuillez commander au moins un produit",
|
||||
"error_message": error_message,
|
||||
},
|
||||
)
|
||||
else:
|
||||
order.compute_order_price()
|
||||
# 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))
|
||||
|
||||
# check if the order contains articles
|
||||
error_message = validate_articles_ordered_nb(order)
|
||||
|
||||
# Redisplay the form with error messages if there is an error
|
||||
if error_message:
|
||||
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):
|
||||
"""Confirmation page after a user orders"""
|
||||
|
|
Loading…
Reference in a new issue