week & day stat

This commit is contained in:
Qwann 2016-12-09 21:45:34 +01:00
parent e4c8209df8
commit 3a7ffefacf
4 changed files with 378 additions and 2 deletions

130
kfet/statistic.py Normal file
View file

@ -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

View file

@ -0,0 +1,88 @@
<!doctype html>
{% load staticfiles %}
<body>
<canvas id="{{ chart_id }}"></canvas>
{% comment %}
TODO : supprimer les fichiers script jQuery et Chart.js
{% endcomment %}
<script src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
<script>
jQuery(document).ready(function() {
var ctx1 = $({{ chart_id }});
var myChart = new Chart(ctx1, {
type: 'line',
data: {
labels: [
{% for k,label in labels.items %}
{% if forloop.last %}
"{{ label }}"
{% else %}
"{{ label }}",
{% endif %}
{% endfor %}
],
datasets: [{
label: 'Toutes consomations',
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgb(255, 99, 132)',
data: [
{% for k,nb in nb_ventes.items %}
{% if forloop.last %}
"{{ nb }}"
{% else %}
"{{ nb }}",
{% endif %}
{% endfor %}
],
fill: false,
lineTension: 0,
} , {
label: 'LIQ',
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgb(54, 162, 235)',
data: [
{% for k,nb in nb_liq.items %}
{% if forloop.last %}
"{{ nb }}"
{% else %}
"{{ nb }}",
{% endif %}
{% endfor %}
],
fill: false,
lineTension: 0,
} , {
label: 'Comptes K-Fêt',
borderColor: 'rgb(255, 205, 86)',
backgroundColor: 'rgb(255, 205, 86)',
data: [
{% for k,nb in nb_accounts.items %}
{% if forloop.last %}
"{{ nb }}"
{% else %}
"{{ nb }}",
{% endif %}
{% endfor %}
],
fill: false,
lineTension: 0,
}]
},
options: {
responsive: true,
tooltips: {
mode: 'nearest',
intersect: false,
},
hover: {
mode: 'nearest',
intersect: false,
},
}
});
});
</script>
</body>

View file

@ -121,6 +121,14 @@ urlpatterns = [
teamkfet_required(views.ArticleUpdate.as_view()),
name = 'kfet.article.update'),
# Article - Statistics
url('^articles/(?P<pk>\d+)/stat/week$',
views.ArticleStatWeek.as_view(),
name = 'kfet.article.stats.week'),
url('^articles/(?P<pk>\d+)/stat/day$',
views.ArticleStatDay.as_view(),
name = 'kfet.article.stats.day'),
# -----
# K-Psul urls
# -----

View file

@ -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)