From 61feb9bbcdb391f23478036c65a32fb48a388ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 28 Aug 2016 05:39:34 +0200 Subject: [PATCH] Ajout livraison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Possible de passer une livraison à un fournisseur - Proposition de quantités générées à partir des ventes sur les 5 dernières semaines - Mail généré à partir d'une commande (pas d'envoi auto) - box_capacity et box_type passe de SupplierArticle à Article --- kfet/forms.py | 35 +++- kfet/migrations/0038_auto_20160828_0402.py | 36 ++++ kfet/migrations/0039_auto_20160828_0430.py | 24 +++ kfet/models.py | 32 ++-- kfet/templates/kfet/order.html | 30 +++- kfet/templates/kfet/order_create.html | 62 +++++++ kfet/templates/kfet/order_read.html | 14 ++ kfet/urls.py | 5 + kfet/views.py | 181 ++++++++++++++++++++- 9 files changed, 391 insertions(+), 28 deletions(-) create mode 100644 kfet/migrations/0038_auto_20160828_0402.py create mode 100644 kfet/migrations/0039_auto_20160828_0430.py create mode 100644 kfet/templates/kfet/order_create.html create mode 100644 kfet/templates/kfet/order_read.html diff --git a/kfet/forms.py b/kfet/forms.py index 709d62ff..b615ba38 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -218,11 +218,13 @@ class ArticleForm(forms.ModelForm): class Meta: model = Article - fields = ['name', 'is_sold', 'price', 'stock', 'category'] + fields = ['name', 'is_sold', 'price', 'stock', 'category', 'box_type', + 'box_capacity'] class ArticleRestrictForm(ArticleForm): class Meta(ArticleForm.Meta): - fields = ['name', 'is_sold', 'price', 'category'] + fields = ['name', 'is_sold', 'price', 'category', 'box_type', + 'box_capacity'] # ----- # K-Psul forms @@ -416,3 +418,32 @@ class InventoryArticleForm(forms.Form): self.stock_old = kwargs['initial']['stock_old'] self.category = kwargs['initial']['category'] self.category_name = kwargs['initial']['category__name'] + +# ----- +# Order forms +# ----- + +class OrderArticleForm(forms.Form): + article = forms.ModelChoiceField( + queryset = Article.objects.all(), + widget = forms.HiddenInput(), + ) + quantity_ordered = forms.IntegerField(required = False) + + def __init__(self, *args, **kwargs): + super(OrderArticleForm, self).__init__(*args, **kwargs) + if 'initial' in kwargs: + self.name = kwargs['initial']['name'] + self.stock = kwargs['initial']['stock'] + self.category = kwargs['initial']['category'] + self.category_name = kwargs['initial']['category__name'] + self.box_capacity = kwargs['initial']['box_capacity'] + self.v_s1 = kwargs['initial']['v_s1'] + self.v_s2 = kwargs['initial']['v_s2'] + self.v_s3 = kwargs['initial']['v_s3'] + self.v_s4 = kwargs['initial']['v_s4'] + self.v_s5 = kwargs['initial']['v_s5'] + self.v_moy = kwargs['initial']['v_moy'] + self.v_et = kwargs['initial']['v_et'] + self.v_prev = kwargs['initial']['v_prev'] + self.c_rec = kwargs['initial']['c_rec'] diff --git a/kfet/migrations/0038_auto_20160828_0402.py b/kfet/migrations/0038_auto_20160828_0402.py new file mode 100644 index 00000000..215591b7 --- /dev/null +++ b/kfet/migrations/0038_auto_20160828_0402.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0037_auto_20160826_2333'), + ] + + operations = [ + migrations.AlterModelOptions( + name='inventory', + options={'ordering': ['-at']}, + ), + migrations.RemoveField( + model_name='supplierarticle', + name='box_capacity', + ), + migrations.RemoveField( + model_name='supplierarticle', + name='box_type', + ), + migrations.AddField( + model_name='article', + name='box_capacity', + field=models.PositiveSmallIntegerField(blank=True, null=True, default=None), + ), + migrations.AddField( + model_name='article', + name='box_type', + field=models.CharField(max_length=7, blank=True, null=True, default=None, choices=[('caisse', 'Caisse'), ('carton', 'Carton'), ('palette', 'Palette'), ('fût', 'Fût')]), + ), + ] diff --git a/kfet/migrations/0039_auto_20160828_0430.py b/kfet/migrations/0039_auto_20160828_0430.py new file mode 100644 index 00000000..271fda68 --- /dev/null +++ b/kfet/migrations/0039_auto_20160828_0430.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0038_auto_20160828_0402'), + ] + + operations = [ + migrations.AlterField( + model_name='order', + name='amount', + field=models.DecimalField(default=0, decimal_places=2, max_digits=6), + ), + migrations.AlterField( + model_name='orderarticle', + name='quantity_received', + field=models.IntegerField(default=0), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 907c6938..7a206d6e 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -325,6 +325,18 @@ class Article(models.Model): category = models.ForeignKey( ArticleCategory, on_delete = models.PROTECT, related_name = "articles") + BOX_TYPE_CHOICES = ( + ("caisse", "caisse"), + ("carton", "carton"), + ("palette", "palette"), + ("fût", "fût"), + ) + box_type = models.CharField( + choices = BOX_TYPE_CHOICES, + max_length = choices_length(BOX_TYPE_CHOICES), + blank = True, null = True, default = None) + box_capacity = models.PositiveSmallIntegerField( + blank = True, null = True, default = None) def __str__(self): return '%s - %s' % (self.category.name, self.name) @@ -391,18 +403,6 @@ class SupplierArticle(models.Model): Supplier, on_delete = models.PROTECT) article = models.ForeignKey( Article, on_delete = models.PROTECT) - BOX_TYPE_CHOICES = ( - ("caisse", "Caisse"), - ("carton", "Carton"), - ("palette", "Palette"), - ("fût", "Fût"), - ) - box_type = models.CharField( - choices = BOX_TYPE_CHOICES, - max_length = choices_length(BOX_TYPE_CHOICES), - blank = True, null = True, default = None) - box_capacity = models.PositiveSmallIntegerField( - blank = True, null = True, default = None) price_HT = models.DecimalField( max_digits = 7, decimal_places = 4, blank = True, null = True, default = None) @@ -422,7 +422,11 @@ class Order(models.Model): through = "OrderArticle", related_name = "orders") at = models.DateTimeField(auto_now_add = True) - amount = models.DecimalField(max_digits = 6, decimal_places = 2) + amount = models.DecimalField( + max_digits = 6, decimal_places = 2, default = 0) + + class Meta: + ordering = ['-at'] class OrderArticle(models.Model): order = models.ForeignKey( @@ -430,7 +434,7 @@ class OrderArticle(models.Model): article = models.ForeignKey( Article, on_delete = models.PROTECT) quantity_ordered = models.IntegerField() - quantity_received = models.IntegerField() + quantity_received = models.IntegerField(default = 0) class TransferGroup(models.Model): at = models.DateTimeField(auto_now_add = True) diff --git a/kfet/templates/kfet/order.html b/kfet/templates/kfet/order.html index 7901a654..28e3d7a9 100644 --- a/kfet/templates/kfet/order.html +++ b/kfet/templates/kfet/order.html @@ -17,19 +17,13 @@
{% include 'kfet/base_messages.html' %}
-
-

Liste des commandes

-
- -
-
-

Fournisseurs

+ @@ -39,6 +33,11 @@ {% for supplier in suppliers %} +
Nom Mail
+ + Passer une commande + + @@ -55,6 +54,23 @@
+
+

Liste des commandes

+
+ + {% for order in orders %} + + + + + {% endfor %} +
+ + {{ order.at }} + + {{ order.supplier }}
+
+
diff --git a/kfet/templates/kfet/order_create.html b/kfet/templates/kfet/order_create.html new file mode 100644 index 00000000..efd316a6 --- /dev/null +++ b/kfet/templates/kfet/order_create.html @@ -0,0 +1,62 @@ +{% extends 'kfet/base.html' %} + +{% block title %}Nouvelle commande{% endblock %} +{% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %} + +{% block content %} + +
+ {% csrf_token %} + + + + + + + + + + + + + + + + + + + + + + {% for form in formset %} + {% ifchanged form.category %} + + + + {% endifchanged %} + + {{ form.article }} + + + + + + + + + + + + + + {% endfor %} + +
ArticleVentesV. moy.E.T.Prév.StockRec.Com.
S1S2S3S4S5
{{ form.category_name }}
{{ form.name }}{{ form.v_s1 }}{{ form.v_s2 }}{{ form.v_s3 }}{{ form.v_s4 }}{{ form.v_s5 }}{{ form.v_moy }}{{ form.v_et }}{{ form.v_prev }}{{ form.stock }}{{ form.c_rec }}{{ form.quantity_ordered }}
+ {% if not perms.kfet.add_order %} + + {% endif %} + {{ formset.management_form }} + +
+ +{% endblock %} diff --git a/kfet/templates/kfet/order_read.html b/kfet/templates/kfet/order_read.html new file mode 100644 index 00000000..beffd359 --- /dev/null +++ b/kfet/templates/kfet/order_read.html @@ -0,0 +1,14 @@ +{% extends 'kfet/base.html' %} + +{% block title %}Commande #{{ order.pk }}{% endblock %} +{% block content-header-title %}Commande #{{ order.pk }}{% endblock %} + +{% block content %} + +

+Créée le {{ order.at }} pour {{ order.supplier.name }} +

+ + + +{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index 5ac6047b..5edd6d77 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -179,7 +179,12 @@ urlpatterns = [ url(r'^orders/$', permission_required('kfet.is_team')(views.OrderList.as_view()), name = 'kfet.order'), + 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$', permission_required('kfet.is_team')(views.SupplierUpdate.as_view()), name = 'kfet.order.supplier.update'), + url(r'^orders/suppliers/(?P\d+)/new-order$', views.order_create, + name = 'kfet.order.new'), ] diff --git a/kfet/views.py b/kfet/views.py index 985acec4..0dfa286c 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -12,19 +12,21 @@ from django.contrib.auth.models import User, Permission, Group from django.http import HttpResponse, JsonResponse, Http404 from django.forms import modelformset_factory, formset_factory from django.db import IntegrityError, transaction -from django.db.models import F, Sum, Prefetch, Count +from django.db.models import F, Sum, Prefetch, Count, Func from django.db.models.functions import Coalesce from django.utils import timezone from django.utils.crypto import get_random_string from gestioncof.models import CofProfile, Clipper from kfet.models import (Account, Checkout, Article, Settings, AccountNegative, CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory, - InventoryArticle, Order) + InventoryArticle, Order, OrderArticle) from kfet.forms import * from collections import defaultdict from kfet import consumers -import datetime +from datetime import timedelta import django_cas_ng +import heapq +import statistics @login_required def home(request): @@ -1293,7 +1295,6 @@ def inventory_create(request): .order_by('category__name', 'name') ) initial = [] - data = [] for article in articles: initial.append({ 'article' : article.pk, @@ -1324,6 +1325,7 @@ def inventory_create(request): if form.cleaned_data['stock_new'] is not None: if not saved: inventory.save() + saved = True article = articles.get(pk=form.cleaned_data['article'].pk) stock_old = article.stock @@ -1356,13 +1358,182 @@ def inventory_create(request): class OrderList(ListView): model = Order template_name = 'kfet/order.html' - context_object_name = 'inventories' + context_object_name = 'orders' def get_context_data(self, **kwargs): context = super(OrderList, self).get_context_data(**kwargs) context['suppliers'] = Supplier.objects.order_by('name') return context +@permission_required('kfet.is_team') +def order_create(request, pk): + supplier = Supplier.objects.get(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 + .select_related('group') + .filter(article__in=articles, canceled_at=None) + .values('article')) + sales_s1 = (sales_q + .filter( + group__at__gte = today-timedelta(weeks=5), + group__at__lt = today-timedelta(weeks=4)) + .annotate(nb=Sum('article_nb')) + ) + sales_s1 = { d['article']:d['nb'] for d in sales_s1 } + sales_s2 = (sales_q + .filter( + group__at__gte = today-timedelta(weeks=4), + group__at__lt = today-timedelta(weeks=3)) + .annotate(nb=Sum('article_nb')) + ) + sales_s2 = { d['article']:d['nb'] for d in sales_s2 } + sales_s3 = (sales_q + .filter( + group__at__gte = today-timedelta(weeks=3), + group__at__lt = today-timedelta(weeks=2)) + .annotate(nb=Sum('article_nb')) + ) + sales_s3 = { d['article']:d['nb'] for d in sales_s3 } + sales_s4 = (sales_q + .filter( + group__at__gte = today-timedelta(weeks=2), + group__at__lt = today-timedelta(weeks=1)) + .annotate(nb=Sum('article_nb')) + ) + sales_s4 = { d['article']:d['nb'] for d in sales_s4 } + sales_s5 = (sales_q + .filter(group__at__gte = today-timedelta(weeks=1)) + .annotate(nb=Sum('article_nb')) + ) + sales_s5 = { d['article']:d['nb'] for d in sales_s5 } + + for article in articles: + v_s1 = sales_s1.get(article.pk, 0) + v_s2 = sales_s2.get(article.pk, 0) + v_s3 = sales_s3.get(article.pk, 0) + v_s4 = sales_s4.get(article.pk, 0) + v_s5 = sales_s5.get(article.pk, 0) + v_all = [v_s1, v_s2, v_s3, v_s4, v_s5] + v_3max = heapq.nlargest(3, v_all) + v_moy = statistics.mean(v_3max) + v_et = statistics.pstdev(v_3max, v_moy) + 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 + if c_rec_temp >= 10: + c_rec = round(c_rec_temp) + elif c_rec_temp > 5: + c_rec = 10 + elif c_rec_temp > 2: + c_rec = 5 + else: + c_rec = round(c_rec_temp) + initial.append({ + 'article': article.pk, + 'name': article.name, + 'category': article.category_id, + 'category__name': article.category.name, + 'stock': article.stock, + 'box_capacity': article.box_capacity, + 'v_s1': v_s1, + 'v_s2': v_s2, + 'v_s3': v_s3, + 'v_s4': v_s4, + 'v_s5': v_s5, + 'v_moy': round(v_moy), + 'v_et': round(v_et), + 'v_prev': round(v_prev), + 'c_rec': article.box_capacity and c_rec or round(c_rec_tot), + }) + + cls_formset = formset_factory( + form = OrderArticleForm, + extra = 0) + + if request.POST: + formset = cls_formset(request.POST, initial=initial) + + if not request.user.has_perm('kfet.add_order'): + messages.error(request, 'Permission refusée') + elif formset.is_valid(): + order = Order() + order.supplier = supplier + saved = False + for form in formset: + if form.cleaned_data['quantity_ordered'] is not None: + if not saved: + order.save() + saved = True + + 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 + OrderArticle.objects.create( + order = order, + article = article, + quantity_ordered = q_ordered) + if saved: + messages.success(request, 'Commande créée') + return redirect('kfet.order.read', order.pk) + messages.warning(request, 'Rien commandé => Pas de commande') + else: + messages.error('Corrigez les erreurs') + else: + formset = cls_formset(initial=initial) + + return render(request, 'kfet/order_create.html', { + 'supplier': supplier, + 'formset' : formset, + }) + +class OrderRead(DetailView): + model = Order + template_name = 'kfet/order_read.html' + context_object_name = 'order' + + def get_context_data(self, **kwargs): + context = super(OrderRead, self).get_context_data(**kwargs) + orderarticles = (OrderArticle.objects + .select_related('article', 'article__category') + .filter(order=self.object) + .order_by('article__category__name', 'article__name') + ) + mail = ("Bonjour,\n\nNous voudrions pour le ##DATE## à la K-Fêt de " + "l'ENS Ulm :") + category = 0 + for orderarticle in orderarticles: + if category != orderarticle.article.category: + category = orderarticle.article.category + mail += '\n' + nb = orderarticle.quantity_ordered + box = '' + if orderarticle.article.box_capacity: + nb /= orderarticle.article.box_capacity + if nb >= 2: + box = ' %ss de' % orderarticle.article.box_type + else: + box = ' %s de' % orderarticle.article.box_type + name = orderarticle.article.name.capitalize() + mail += "\n- %s%s %s" % (round(nb), box, name) + + mail += ("\n\nMerci d'appeler le numéro suivant lorsque les livreurs " + "sont là : ##TELEPHONE##\nCordialement,\n##PRENOM## ##NOM## " + ", pour la K-Fêt de l'ENS Ulm") + + context['mail'] = mail + return context + class SupplierUpdate(SuccessMessageMixin, UpdateView): model = Supplier template_name = 'kfet/supplier_form.html'