From 9467103879cd45b4e9d28c2f6d4ef1b4ee369e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Tue, 30 Aug 2016 15:35:30 +0200 Subject: [PATCH] Inventaire depuis une commande MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Possible de générer un inventaire à partir d'une commande passée. Préremplissage avec les valeurs commandées. - Possible d'indiquer les prix d'achat pour avoir l'historique des prix d'un article chez un fournisseur. Et bientôt, une proposition automatique de prix. - L'erreur sur le stock d'un article lors d'un inventaire n'est pas mise à jour dans le cas où l'inventaire est généré à partir d'une commande. - Ajout d'un champ `at` au modèle `SupplierArticle` afin de conserver l'historique des prix d'achat - Fix sur la vue `order_create` --- kfet/forms.py | 24 ++++++ kfet/migrations/0040_auto_20160829_2035.py | 31 ++++++++ kfet/migrations/0041_auto_20160830_1502.py | 18 +++++ kfet/models.py | 7 +- kfet/templates/kfet/base_nav.html | 1 + kfet/templates/kfet/order.html | 8 ++ kfet/templates/kfet/order_to_inventory.html | 48 ++++++++++++ kfet/urls.py | 4 +- kfet/views.py | 84 +++++++++++++++++++-- 9 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 kfet/migrations/0040_auto_20160829_2035.py create mode 100644 kfet/migrations/0041_auto_20160830_1502.py create mode 100644 kfet/templates/kfet/order_to_inventory.html diff --git a/kfet/forms.py b/kfet/forms.py index b615ba38..7826cb41 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -447,3 +447,27 @@ class OrderArticleForm(forms.Form): self.v_et = kwargs['initial']['v_et'] self.v_prev = kwargs['initial']['v_prev'] self.c_rec = kwargs['initial']['c_rec'] + +class OrderArticleToInventoryForm(forms.Form): + article = forms.ModelChoiceField( + queryset = Article.objects.all(), + widget = forms.HiddenInput(), + ) + price_HT = forms.DecimalField( + max_digits = 7, decimal_places = 4, + required = False) + TVA = forms.DecimalField( + max_digits = 7, decimal_places = 2, + required = False) + rights = forms.DecimalField( + max_digits = 7, decimal_places = 4, + required = False) + quantity_received = forms.IntegerField() + + def __init__(self, *args, **kwargs): + super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs) + if 'initial' in kwargs: + self.name = kwargs['initial']['name'] + self.category = kwargs['initial']['category'] + self.category_name = kwargs['initial']['category__name'] + self.quantity_ordered = kwargs['initial']['quantity_ordered'] diff --git a/kfet/migrations/0040_auto_20160829_2035.py b/kfet/migrations/0040_auto_20160829_2035.py new file mode 100644 index 00000000..78b577a8 --- /dev/null +++ b/kfet/migrations/0040_auto_20160829_2035.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import datetime +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0039_auto_20160828_0430'), + ] + + operations = [ + migrations.AlterModelOptions( + name='order', + options={'ordering': ['-at']}, + ), + migrations.AddField( + model_name='supplierarticle', + name='at', + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 8, 29, 18, 35, 3, 419033, tzinfo=utc)), + preserve_default=False, + ), + migrations.AlterField( + model_name='article', + name='box_type', + field=models.CharField(choices=[('caisse', 'caisse'), ('carton', 'carton'), ('palette', 'palette'), ('fût', 'fût')], null=True, max_length=7, blank=True, default=None), + ), + ] diff --git a/kfet/migrations/0041_auto_20160830_1502.py b/kfet/migrations/0041_auto_20160830_1502.py new file mode 100644 index 00000000..40c83907 --- /dev/null +++ b/kfet/migrations/0041_auto_20160830_1502.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0040_auto_20160829_2035'), + ] + + operations = [ + migrations.AlterModelOptions( + name='globalpermissions', + options={'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'), ('order_to_inventory', "Générer un inventaire à partir d'une commande")), 'managed': False}, + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 7a206d6e..59448d52 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -381,7 +381,10 @@ class InventoryArticle(models.Model): stock_error = models.IntegerField(default = 0) def save(self, *args, **kwargs): - self.stock_error = self.stock_new - self.stock_old + # S'il s'agit d'un inventaire provenant d'une livraison, il n'y a pas + # d'erreur + if not hasattr(self.inventory, 'order'): + self.stock_error = self.stock_new - self.stock_old super(InventoryArticle, self).save(*args, **kwargs) class Supplier(models.Model): @@ -403,6 +406,7 @@ class SupplierArticle(models.Model): Supplier, on_delete = models.PROTECT) article = models.ForeignKey( Article, on_delete = models.PROTECT) + at = models.DateTimeField(auto_now_add = True) price_HT = models.DecimalField( max_digits = 7, decimal_places = 4, blank = True, null = True, default = None) @@ -543,6 +547,7 @@ class GlobalPermissions(models.Model): ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'), + ('order_to_inventory', "Générer un inventaire à partir d'une commande") ) class Settings(models.Model): diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index 8b30f0f3..141ff78c 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -35,6 +35,7 @@
  • Caisses
  • Articles
  • Historique
  • +
  • Commandes
  • {% if user.username != 'kfet_genericteam' %}
  • Connexion standard
  • {% endif %} diff --git a/kfet/templates/kfet/order.html b/kfet/templates/kfet/order.html index 28e3d7a9..d9441c15 100644 --- a/kfet/templates/kfet/order.html +++ b/kfet/templates/kfet/order.html @@ -66,6 +66,14 @@ {{ order.supplier }} + + {% if order.inventory %} + Inventaire associé + {% else %} + + Maj stock + + {% endif %} {% endfor %} diff --git a/kfet/templates/kfet/order_to_inventory.html b/kfet/templates/kfet/order_to_inventory.html new file mode 100644 index 00000000..4345cf2b --- /dev/null +++ b/kfet/templates/kfet/order_to_inventory.html @@ -0,0 +1,48 @@ +{% extends 'kfet/base.html' %} + +{% block title %}{% endblock %} +{% block content-header-title %}{% endblock %} + +{% block content %} + +{% include 'kfet/base_messages.html' %} + +
    + {% csrf_token %} + + + + + + + + + + + + + {% for form in formset %} + {% ifchanged form.category %} + + + + {% endifchanged %} + + {{ form.article }} + + + + + + + {% endfor %} + +
    ArticleHTTVADroitsCom.Reçu
    {{ form.category_name }}
    {{ form.name }}{{ form.price_HT }}{{ form.TVA }}{{ form.rights }}{{ form.quantity_ordered }}{{ form.quantity_received }}
    + {% if not perms.kfet.order_to_inventory %} + + {% endif %} + {{ formset.management_form }} + +
    + +{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index 5edd6d77..18718aa0 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -179,7 +179,7 @@ urlpatterns = [ url(r'^orders/$', permission_required('kfet.is_team')(views.OrderList.as_view()), name = 'kfet.order'), - url(r'^orders/(?P\d+)', + url(r'^orders/(?P\d+)$', permission_required('kfet.is_team')(views.OrderRead.as_view()), name = 'kfet.order.read'), url(r'^orders/suppliers/(?P\d+)/edit$', @@ -187,4 +187,6 @@ urlpatterns = [ name = 'kfet.order.supplier.update'), url(r'^orders/suppliers/(?P\d+)/new-order$', views.order_create, name = 'kfet.order.new'), + url(r'^orders/(?P\d+)/to_inventory$', views.order_to_inventory, + name = 'kfet.order.to_inventory'), ] diff --git a/kfet/views.py b/kfet/views.py index 0dfa286c..116c1f3b 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1367,15 +1367,13 @@ class OrderList(ListView): @permission_required('kfet.is_team') def order_create(request, pk): - supplier = Supplier.objects.get(pk=pk) + supplier = get_object_or_404(Supplier, pk=pk) articles = (Article.objects .filter(suppliers=supplier.pk) .select_related('category') .order_by('category__name', 'name')) - print(articles[0].suppliers.all()) - initial = [] today = timezone.now().date() sales_q = (Operation.objects @@ -1429,7 +1427,7 @@ def order_create(request, pk): v_prev = v_moy + v_et c_rec_tot = max(v_prev * 1.5 - article.stock, 0) if article.box_capacity: - c_rec_temp = c_rec_tot / box_capacity + c_rec_temp = c_rec_tot / article.box_capacity if c_rec_temp >= 10: c_rec = round(c_rec_temp) elif c_rec_temp > 5: @@ -1478,7 +1476,7 @@ def order_create(request, pk): article = articles.get(pk=form.cleaned_data['article'].pk) q_ordered = form.cleaned_data['quantity_ordered'] if article.box_capacity: - q_ordered /= article.box_capacity + q_ordered *= article.box_capacity OrderArticle.objects.create( order = order, article = article, @@ -1534,6 +1532,82 @@ class OrderRead(DetailView): context['mail'] = mail return context +def order_to_inventory(request, pk): + order = get_object_or_404(Order, pk=pk) + + if hasattr(order, 'inventory'): + raise Http404 + + articles = (Article.objects + .filter(orders=order.pk) + .select_related('category') + .prefetch_related(Prefetch('orderarticle_set', + queryset = OrderArticle.objects.filter(order=order), + to_attr = 'order')) + .prefetch_related(Prefetch('supplierarticle_set', + queryset = (SupplierArticle.objects + .filter(supplier=order.supplier) + .order_by('-at')), + to_attr = 'supplier')) + .order_by('category__name', 'name')) + + initial = [] + for article in articles: + initial.append({ + 'article': article.pk, + 'name': article.name, + 'category': article.category_id, + 'category__name': article.category.name, + 'quantity_ordered': article.order[0].quantity_ordered, + 'quantity_received': article.order[0].quantity_ordered, + 'price_HT': article.supplier[0].price_HT, + 'TVA': article.supplier[0].TVA, + 'rights': article.supplier[0].rights, + }) + + cls_formset = formset_factory(OrderArticleToInventoryForm, extra=0) + + if request.method == 'POST': + formset = cls_formset(request.POST, initial=initial) + + if not request.user.has_perm('kfet.order_to_inventory'): + message.error(request, 'Permission refusée') + elif formset.is_valid(): + with transaction.atomic(): + inventory = Inventory() + inventory.order = order + inventory.by = request.user.profile.account_kfet + inventory.save() + for form in formset: + q_received = form.cleaned_data['quantity_received'] + article = form.cleaned_data['article'] + SupplierArticle.objects.create( + supplier = order.supplier, + article = article, + price_HT = form.cleaned_data['price_HT'], + TVA = form.cleaned_data['TVA'], + rights = form.cleaned_data['rights']) + (OrderArticle.objects + .filter(order=order, article=article) + .update(quantity_received = q_received)) + InventoryArticle.objects.create( + inventory = inventory, + article = article, + stock_old = article.stock, + stock_new = article.stock + q_received) + article.stock += q_received + article.save() + messages.success(request, "C'est tout bon !") + return redirect('kfet.order') + else: + messages.error(request, "Corrigez les erreurs") + else: + formset = cls_formset(initial=initial) + + return render(request, 'kfet/order_to_inventory.html', { + 'formset': formset, + }) + class SupplierUpdate(SuccessMessageMixin, UpdateView): model = Supplier template_name = 'kfet/supplier_form.html'