forked from DGNum/gestioCOF
week & day stat
This commit is contained in:
parent
e4c8209df8
commit
3a7ffefacf
4 changed files with 378 additions and 2 deletions
130
kfet/statistic.py
Normal file
130
kfet/statistic.py
Normal 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
|
88
kfet/templates/kfet/article_stat.html
Normal file
88
kfet/templates/kfet/article_stat.html
Normal 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>
|
|
@ -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
|
||||
# -----
|
||||
|
|
154
kfet/views.py
154
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)
|
||||
|
|
Loading…
Reference in a new issue