diff --git a/kfet/statistic.py b/kfet/statistic.py
new file mode 100644
index 00000000..31db25e4
--- /dev/null
+++ b/kfet/statistic.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+from django.utils import timezone
+
+french_days = {
+ 1: "lundi",
+ 2: "mardi",
+ 3: "mercredi",
+ 4: "jeudi",
+ 5: "vendredi",
+ 6: "samedi",
+ 7: "dimanche",
+ }
+
+french_months = {
+ 1: "janvier",
+ 2: "février",
+ 3: "mars",
+ 4: "avril",
+ 5: "mai",
+ 6: "juin",
+ 7: "juillet",
+ 8: "août",
+ 9: "septembre",
+ 10: "octobre",
+ 11: "novembre",
+ 12: "décembre",
+ }
+
+
+def dayname(date):
+ return french_days[date.isoweekday()]
+
+
+def weekname(date):
+ (_, a, _) = date.isocalendar()
+ week_num = a
+ return "semaine %d" % week_num
+
+
+def monthname(date):
+ return french_months[date.month]
+
+
+# Pareil mais pour une liste de dates
+# dans un dico ordonné
+def daynames(dates):
+ names = {}
+ for i in dates:
+ names[i] = dayname(dates[i])
+ return names
+
+
+# Pareil mais pour une liste de dates
+# dans un dico ordonné
+def weeksnames(dates):
+ names = {}
+ for i in dates:
+ names[i] = weekname(dates[i])
+ return names
+
+
+# Pareil mais pour une liste de dates
+# dans un dico ordonné
+def monthnames(dates):
+ names = {}
+ for i in dates:
+ names[i] = monthname(dates[i])
+ return names
+
+
+# rend les dates des nb derniers jours
+# dans l'ordre chronologique
+# aujourd'hui compris
+# nb = 1 : rend hier
+def lastdays(nb):
+ morning = this_morning()
+ days = {}
+ for i in range(1, nb+1):
+ days[i] = morning - timezone.timedelta(days=nb - i + 1)
+ return days
+
+
+def lastweeks(nb):
+ monday_morning = this_monday_morning()
+ mondays = {}
+ for i in range(1, nb+1):
+ mondays[i] = monday_morning \
+ - timezone.timedelta(days=7*(nb - i + 1))
+ return mondays
+
+
+# def lastmonths(nb):
+# first_month_day = this_first_month_day()
+# fisrt_days = {}
+# for i in range(1, nb+1):
+# days[i] =
+
+
+def this_first_month_day():
+ now = timezone.now()
+ first_day = timezone.datetime(year=now.year,
+ month=now.month,
+ day=1)
+ return first_day
+
+
+def this_monday_morning():
+ now = timezone.now()
+ monday = now - timezone.timedelta(days=now.isoweekday()-1)
+ monday_morning = timezone.datetime(year=monday.year,
+ month=monday.month,
+ day=monday.day)
+ return monday_morning
+
+
+def this_morning():
+ now = timezone.now()
+ morning = timezone.datetime(year=now.year,
+ month=now.month,
+ day=now.day)
+ return morning
+
+
+# Étant donné un queryset d'operations
+# rend la somme des article_nb
+def tot_ventes(queryset):
+ res = 0
+ for op in queryset:
+ res += op.article_nb
+ return res
diff --git a/kfet/templates/kfet/article_stat.html b/kfet/templates/kfet/article_stat.html
new file mode 100644
index 00000000..e60c4a90
--- /dev/null
+++ b/kfet/templates/kfet/article_stat.html
@@ -0,0 +1,88 @@
+
+{% load staticfiles %}
+
+
+
+
+{% comment %}
+TODO : supprimer les fichiers script jQuery et Chart.js
+{% endcomment %}
+
+
+
+
diff --git a/kfet/urls.py b/kfet/urls.py
index 9b9ebf21..ad460f19 100644
--- a/kfet/urls.py
+++ b/kfet/urls.py
@@ -121,6 +121,14 @@ urlpatterns = [
teamkfet_required(views.ArticleUpdate.as_view()),
name = 'kfet.article.update'),
+ # Article - Statistics
+ url('^articles/(?P\d+)/stat/week$',
+ views.ArticleStatWeek.as_view(),
+ name = 'kfet.article.stats.week'),
+ url('^articles/(?P\d+)/stat/day$',
+ views.ArticleStatDay.as_view(),
+ name = 'kfet.article.stats.day'),
+
# -----
# K-Psul urls
# -----
diff --git a/kfet/views.py b/kfet/views.py
index f95fb2c6..e973d27b 100644
--- a/kfet/views.py
+++ b/kfet/views.py
@@ -8,6 +8,7 @@ from django.shortcuts import render, get_object_or_404, redirect
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.cache import cache
from django.views.generic import ListView, DetailView
+from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
from django.core.urlresolvers import reverse_lazy
from django.contrib import messages
@@ -26,7 +27,7 @@ from gestioncof.models import CofProfile, Clipper
from kfet.decorators import teamkfet_required
from kfet.models import (Account, Checkout, Article, Settings, AccountNegative,
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
- InventoryArticle, Order, OrderArticle)
+ InventoryArticle, Order, OrderArticle, Operation)
from kfet.forms import *
from collections import defaultdict
from kfet import consumers
@@ -35,10 +36,12 @@ import django_cas_ng
import hashlib
import heapq
import statistics
+from .statistic import lastdays, daynames, this_morning,\
+ tot_ventes, weeksnames, this_monday_morning, lastweeks
@login_required
def home(request):
- return render(request, "kfet/base.html")
+ return render(request, "kfet/home.html")
@teamkfet_required
def login_genericteam(request):
@@ -793,6 +796,8 @@ class ArticleUpdate(SuccessMessageMixin, UpdateView):
# Updating
return super(ArticleUpdate, self).form_valid(form)
+
+
# -----
# K-Psul
# -----
@@ -1941,3 +1946,148 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView):
return self.form_invalid(form)
# Updating
return super(SupplierUpdate, self).form_valid(form)
+
+
+# -----
+# Statistics
+# -----
+
+# Vues génériques
+# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/
+class JSONResponseMixin(object):
+ """
+ A mixin that can be used to render a JSON response.
+ """
+ def render_to_json_response(self, context, **response_kwargs):
+ """
+ Returns a JSON response, transforming 'context' to make the payload.
+ """
+ return JsonResponse(
+ self.get_data(context),
+ **response_kwargs
+ )
+
+ def get_data(self, context):
+ """
+ Returns an object that will be serialized as JSON by json.dumps().
+ """
+ # Note: This is *EXTREMELY* naive; in reality, you'll need
+ # to do much more complex handling to ensure that arbitrary
+ # objects -- such as Django model instances or querysets
+ # -- can be serialized as JSON.
+ return context
+
+
+# Rend un DetailView en html sauf si on lui précise dans
+# l'appel à get que l'on veut un json auquel cas il en rend un
+class HybridDetailView(JSONResponseMixin,
+ SingleObjectTemplateResponseMixin,
+ BaseDetailView):
+ def render_to_response(self, context):
+ # Look for a 'format=json' GET argument
+ if self.request.GET.get('format') == 'json':
+ return self.render_to_json_response(context)
+ else:
+ return super(HybridDetailView, self).render_to_response(context)
+
+
+# Article Statistiques
+
+
+class ArticleStat(HybridDetailView):
+ model = Article
+ template_name = 'kfet/article_stat.html'
+ context_object_name = 'article'
+ end_date = timezone.now()
+ id_prefix = "lol"
+
+ def render_to_response(self, context):
+ # Look for a 'format=json' GET argument
+ if self.request.GET.get('format') == 'json':
+ return self.render_to_json_response(context)
+ else:
+ return super(ArticleStat, self).render_to_response(context)
+
+ # doit rendre un dictionnaire des dates
+ # la première date correspond au début
+ # la dernière date est la fin de la dernière plage
+ def get_dates(self, **kwargs):
+ pass
+
+ # doit rendre un dictionnaire des labels
+ # le dernier label ne sera pas utilisé
+ def get_labels(self, **kwargs):
+ pass
+
+ def get_context_data(self, **kwargs):
+ # On hérite
+ # en fait non, pas besoin et c'est chiant à dumper
+ # context = super(ArticleStat, self).get_context_data(**kwargs)
+ context = {}
+ # On récupère les labels des dates
+ context['labels'] = self.get_labels().copy()
+ # On récupère les dates
+ dates = self.get_dates()
+ # On ajoute la date de fin
+ extended_dates = dates.copy()
+ extended_dates[len(dates)+1] = self.end_date
+ # On selectionne les opérations qui correspondent
+ # à l'article en question et qui ne sont pas annulées
+ # puis on choisi pour chaques intervalle les opérations
+ # effectuées dans ces intervalles de temps
+ all_operations = (Operation.objects
+ .filter(type='purchase')
+ .filter(article=self.object)
+ .filter(canceled_at=None)
+ )
+ operations = {}
+ for i in dates:
+ operations[i] = (all_operations
+ .filter(group__at__gte=extended_dates[i])
+ .filter(group__at__lte=extended_dates[i+1])
+ )
+ # On compte les opérations
+ nb_ventes = {}
+ nb_accounts = {}
+ nb_liq = {}
+ for i in operations:
+ nb_ventes[i] = tot_ventes(operations[i])
+ nb_liq[i] = tot_ventes(
+ operations[i]
+ .filter(group__on_acc__trigramme='LIQ')
+ )
+ nb_accounts[i] = tot_ventes(
+ operations[i]
+ .exclude(group__on_acc__trigramme='LIQ')
+ )
+ context['nb_ventes'] = nb_ventes
+ context['nb_accounts'] = nb_accounts
+ context['nb_liq'] = nb_liq
+ # ID unique
+ context['chart_id'] = "%s_%s" % (self.id_prefix,
+ self.object.name)
+ return context
+
+
+class ArticleStatDay(ArticleStat):
+ end_date = this_morning()
+ id_prefix = "last_week"
+
+ def get_dates(self, **kwargs):
+ return lastdays(7)
+
+ def get_labels(self, **kwargs):
+ days = lastdays(7)
+ return daynames(days)
+
+
+class ArticleStatWeek(ArticleStat):
+ end_date = this_monday_morning()
+ id_prefix = "last_weeks"
+
+ def get_dates(self, **kwargs):
+ return lastweeks(7)
+
+ def get_labels(self, **kwargs):
+ weeks = lastweeks(7)
+ return weeksnames(weeks)