Merge branch 'Aufinal/inventory_delete' into 'master'

Permet l'annulation d'un inventaire

Closes #251

See merge request klub-dev-ens/gestioCOF!457
This commit is contained in:
Martin Pepin 2020-12-04 17:13:32 +01:00
commit 72cd55716b
5 changed files with 154 additions and 3 deletions

View file

@ -21,9 +21,12 @@ Liste des changements notables dans GestioCOF depuis la version 0.1 (septembre
Uniquement un modèle simple de clubs avec des respos. Aucune gestion des Uniquement un modèle simple de clubs avec des respos. Aucune gestion des
adhérents ni des cotisations. adhérents ni des cotisations.
## Version ??? - le futur ## Version ??? - dans un futur proche
... ### K-Fêt
- On peut supprimer un inventaire. Seuls les articles dont c'est le dernier
inventaire sont affectés.
## Version 0.8 - 03/12/2020 ## Version 0.8 - 03/12/2020

View file

@ -19,6 +19,17 @@
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
{% if perms.kfet.delete_inventory %}
<hr>
<div class="buttons">
<button class="btn btn-default" id="button-delete">
<span class="glyphicon glyphicon-remove"></span><span>Annuler l'inventaire</span>
</button>
<form method="post" action="{% url 'kfet.inventory.delete' inventory.pk %}" id="inventory-delete-form">
{% csrf_token %}
</form>
{% endif %}
</div>
</div> </div>
</aside> </aside>
@ -64,4 +75,29 @@
</table> </table>
</div> </div>
<script type="text/javascript">
$( function() {
// Delete button
$('#button-delete').click(function() {
$.confirm({
title: 'Confirmer la suppression',
content: `
<div class="warning">
<span class='glyphicon glyphicon-warning-sign'></span><span>Cette opération est irréversible !</span>
<br>
<span>N.B. : seuls les articles dont c'est le <b>dernier</b> inventaire en date seront affectés.</span>
</div>
`,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
confirm: function() {
$('#inventory-delete-form').submit();
}
})
})
});
</script>
{% endblock %} {% endblock %}

View file

@ -4579,6 +4579,87 @@ class InventoryReadViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
class InventoryDeleteViewTests(ViewTestCaseMixin, TestCase):
url_name = "kfet.inventory.delete"
auth_user = "team1"
auth_forbidden = [None, "user", "team"]
def get_users_extra(self):
return {
"user1": create_user("user1", "001"),
"team1": create_team("team1", "101", perms=["kfet.delete_inventory"]),
}
@property
def url_kwargs(self):
return {"pk": self.inventory1.pk}
@property
def url_expected(self):
return "/k-fet/inventaires/{}/delete".format(self.inventory1.pk)
def setUp(self):
super().setUp()
# Deux inventaires : un avec article 1 + 2, l'autre avec 1 + 3
self.inventory1 = Inventory.objects.create(
by=self.accounts["team"], at=self.now
)
self.inventory2 = Inventory.objects.create(
by=self.accounts["team"], at=self.now + timedelta(days=1)
)
category = ArticleCategory.objects.create(name="Category")
# Le stock des articles correspond à leur dernier inventaire
self.article1 = Article.objects.create(
name="Article1", category=category, stock=51
)
self.article2 = Article.objects.create(
name="Article2", category=category, stock=42
)
self.article3 = Article.objects.create(
name="Article3", category=category, stock=42
)
InventoryArticle.objects.create(
inventory=self.inventory1,
article=self.article1,
stock_old=23,
stock_new=42,
)
InventoryArticle.objects.create(
inventory=self.inventory1,
article=self.article2,
stock_old=23,
stock_new=42,
)
InventoryArticle.objects.create(
inventory=self.inventory2,
article=self.article1,
stock_old=42,
stock_new=51,
)
InventoryArticle.objects.create(
inventory=self.inventory2,
article=self.article3,
stock_old=23,
stock_new=42,
)
def test_ok(self):
r = self.client.post(self.url)
self.assertRedirects(r, reverse("kfet.inventory"))
# On vérifie que l'inventaire n'existe plus
self.assertFalse(Inventory.objects.filter(pk=self.inventory1.pk).exists())
# On check les stocks
self.article1.refresh_from_db()
self.article2.refresh_from_db()
self.article3.refresh_from_db()
self.assertEqual(self.article1.stock, 51)
self.assertEqual(self.article2.stock, 23)
self.assertEqual(self.article3.stock, 42)
class OrderListViewTests(ViewTestCaseMixin, TestCase): class OrderListViewTests(ViewTestCaseMixin, TestCase):
url_name = "kfet.order" url_name = "kfet.order"
url_expected = "/k-fet/orders/" url_expected = "/k-fet/orders/"

View file

@ -270,6 +270,11 @@ urlpatterns = [
teamkfet_required(views.InventoryRead.as_view()), teamkfet_required(views.InventoryRead.as_view()),
name="kfet.inventory.read", name="kfet.inventory.read",
), ),
path(
"inventaires/<int:pk>/delete",
views.InventoryDelete.as_view(),
name="kfet.inventory.delete",
),
# ----- # -----
# Order urls # Order urls
# ----- # -----

View file

@ -13,7 +13,7 @@ from django.contrib.auth.models import Permission, User
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.db import transaction from django.db import transaction
from django.db.models import Count, F, Prefetch, Q, Sum from django.db.models import Count, F, Max, OuterRef, Prefetch, Q, Subquery, Sum
from django.forms import formset_factory from django.forms import formset_factory
from django.http import Http404, HttpResponseBadRequest, JsonResponse from django.http import Http404, HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
@ -1909,6 +1909,32 @@ class InventoryRead(DetailView):
return context return context
class InventoryDelete(PermissionRequiredMixin, DeleteView):
model = Inventory
success_url = reverse_lazy("kfet.inventory")
success_message = "Inventaire annulé avec succès !"
permission_required = "kfet.delete_inventory"
def get(self, request, *args, **kwargs):
return redirect("kfet.inventory.read", self.kwargs.get(self.pk_url_kwarg))
def delete(self, request, *args, **kwargs):
inv = self.get_object()
# On met à jour les articles dont c'est le dernier inventaire
# .get() ne marche pas avec OuterRef, donc on utilise .filter() avec [:1]
update_subquery = InventoryArticle.objects.filter(
inventory=inv, article=OuterRef("pk")
).values("stock_old")[:1]
Article.objects.annotate(last_env=Max("inventories__at")).filter(
last_env=inv.at
).update(stock=Subquery(update_subquery))
# On a tout mis à jour, on peut delete (avec un message)
messages.success(request, self.success_message)
return super().delete(request, *args, **kwargs)
# ----- # -----
# Order views # Order views
# ----- # -----