forked from DGNum/gestioCOF
first fixes
This commit is contained in:
parent
219835be17
commit
5877218af5
2 changed files with 398 additions and 406 deletions
|
@ -1,44 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
french_days = {
|
from django.views.generic import DetailView
|
||||||
1: "lundi",
|
from django.views.generic.list import BaseListView, MultipleObjectTemplateResponseMixin
|
||||||
2: "mardi",
|
from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin
|
||||||
3: "mercredi",
|
from django.contrib.auth.decorators import login_required
|
||||||
4: "jeudi",
|
from kfet.models import (Account, Article, Operation, OperationGroup, Transfer)
|
||||||
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
|
# Pareil mais pour une liste de dates
|
||||||
|
@ -46,7 +13,7 @@ def monthname(date):
|
||||||
def daynames(dates):
|
def daynames(dates):
|
||||||
names = {}
|
names = {}
|
||||||
for i in dates:
|
for i in dates:
|
||||||
names[i] = dayname(dates[i])
|
names[i] = dates[i].strftime("%A")
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +22,7 @@ def daynames(dates):
|
||||||
def weeknames(dates):
|
def weeknames(dates):
|
||||||
names = {}
|
names = {}
|
||||||
for i in dates:
|
for i in dates:
|
||||||
names[i] = weekname(dates[i])
|
names[i] = "Semaine " + dates[i].strftime("%W")
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,7 +31,7 @@ def weeknames(dates):
|
||||||
def monthnames(dates):
|
def monthnames(dates):
|
||||||
names = {}
|
names = {}
|
||||||
for i in dates:
|
for i in dates:
|
||||||
names[i] = monthname(dates[i])
|
names[i] = dates[i].strftime("%B")
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,3 +102,389 @@ def tot_ventes(queryset):
|
||||||
for op in queryset:
|
for op in queryset:
|
||||||
res += op.article_nb
|
res += op.article_nb
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
# -----
|
||||||
|
# Views
|
||||||
|
# -----
|
||||||
|
# ---------------
|
||||||
|
# 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
|
||||||
|
|
||||||
|
class HybridDetailView(JSONResponseMixin,
|
||||||
|
SingleObjectTemplateResponseMixin,
|
||||||
|
BaseDetailView):
|
||||||
|
"""
|
||||||
|
Returns a DetailView as a html page except if it asked a JSON
|
||||||
|
file by the GET method in witch case it returns a JSON response.
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class HybridListView(JSONResponseMixin,
|
||||||
|
MultipleObjectTemplateResponseMixin,
|
||||||
|
BaseListView):
|
||||||
|
"""
|
||||||
|
Returns a ListView as a html page except if it asked a JSON
|
||||||
|
file by the GET method in witch case it returns a JSON response.
|
||||||
|
"""
|
||||||
|
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(HybridListView, self).render_to_response(context)
|
||||||
|
|
||||||
|
class ObjectResumeStat(DetailView):
|
||||||
|
"""
|
||||||
|
Summarize all the stats of an object
|
||||||
|
DOES NOT RETURN A JSON RESPONSE
|
||||||
|
"""
|
||||||
|
template_name = 'kfet/object_stat_resume.html'
|
||||||
|
context_object_name = 'lul'
|
||||||
|
id_prefix = 'id_a_definir'
|
||||||
|
# nombre de vues à résumer
|
||||||
|
nb_stat = 2
|
||||||
|
# Le combienième est celui par defaut ?
|
||||||
|
# (entre 0 et nb_stat-1)
|
||||||
|
nb_default = 0
|
||||||
|
stat_labels = ['stat_1', 'stat_2']
|
||||||
|
stat_urls = ['url_1', 'url_2']
|
||||||
|
|
||||||
|
# sert à renverser les urls
|
||||||
|
# utile de le surcharger quand l'url prend d'autres arguments que l'id
|
||||||
|
def get_object_url_kwargs(self, **kwargs):
|
||||||
|
return {'pk': self.object.id}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
# On hérite pas
|
||||||
|
object_id = self.object.id
|
||||||
|
context = {}
|
||||||
|
stats = {}
|
||||||
|
for i in range(self.nb_stat):
|
||||||
|
stats[i] = {
|
||||||
|
'label': self.stat_labels[i],
|
||||||
|
'btn': "btn_%s_%d_%d" % (self.id_prefix,
|
||||||
|
object_id,
|
||||||
|
i),
|
||||||
|
'url': reverse_lazy(self.stat_urls[i],
|
||||||
|
kwargs=self.get_object_url_kwargs()),
|
||||||
|
}
|
||||||
|
prefix = "%s_%d" % (self.id_prefix, object_id)
|
||||||
|
context['id_prefix'] = prefix
|
||||||
|
context['content_id'] = "content_%s" % prefix
|
||||||
|
context['stats'] = stats
|
||||||
|
context['default_stat'] = self.nb_default
|
||||||
|
context['object_id'] = object_id
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------
|
||||||
|
# AccountStatBalance
|
||||||
|
# ------------------
|
||||||
|
class AccountStatBalance(HybridDetailView):
|
||||||
|
"""
|
||||||
|
Returns a graph (or a JSON Response) of the evolution a the personnal
|
||||||
|
balance of a trigramm between begin_date and end_date
|
||||||
|
takes into account the Operations and the Transfers
|
||||||
|
does not takes intto account the balance offset
|
||||||
|
"""
|
||||||
|
model = Account
|
||||||
|
trigramme_url_kwarg = 'trigramme'
|
||||||
|
template_name = 'kfet/account_stat_balance.html'
|
||||||
|
context_object_name = 'account'
|
||||||
|
begin_date = this_morning()
|
||||||
|
end_date = timezone.now() # ne gère pas encore autre chose que now
|
||||||
|
anytime = False # un cas particulier
|
||||||
|
id_prefix = "lol"
|
||||||
|
|
||||||
|
def get_object(self, **kwargs):
|
||||||
|
trigramme = self.kwargs.get(self.trigramme_url_kwarg)
|
||||||
|
return get_object_or_404(Account, trigramme=trigramme)
|
||||||
|
|
||||||
|
def get_changes_list(self, **kwargs):
|
||||||
|
account = self.object
|
||||||
|
# On récupère les opérations
|
||||||
|
# TODO: retirer les opgroup dont tous les op sont annulées
|
||||||
|
opgroups = list(OperationGroup.objects
|
||||||
|
.filter(on_acc=account)
|
||||||
|
.filter(at__gte=self.begin_date)
|
||||||
|
.filter(at__lte=self.end_date))
|
||||||
|
# On récupère les transferts reçus
|
||||||
|
received_transfers = list(Transfer.objects
|
||||||
|
.filter(to_acc=account)
|
||||||
|
.filter(canceled_at=None)
|
||||||
|
.filter(group__at__gte=self.begin_date)
|
||||||
|
.filter(group__at__lte=self.end_date))
|
||||||
|
# On récupère les transferts émis
|
||||||
|
emitted_transfers = list(Transfer.objects
|
||||||
|
.filter(from_acc=account)
|
||||||
|
.filter(canceled_at=None)
|
||||||
|
.filter(group__at__gte=self.begin_date)
|
||||||
|
.filter(group__at__lte=self.end_date))
|
||||||
|
# On transforme tout ça en une liste de dictionnaires sous la forme
|
||||||
|
# {'at': date,
|
||||||
|
# 'amount': changement de la balance (négatif si diminue la balance,
|
||||||
|
# positif si l'augmente),
|
||||||
|
# 'label': text descriptif,
|
||||||
|
# 'balance': état de la balance après l'action (0 pour le moment,
|
||||||
|
# sera mis à jour lors d'une
|
||||||
|
# autre passe)
|
||||||
|
# }
|
||||||
|
actions = [
|
||||||
|
# Maintenant (à changer si on gère autre chose que now)
|
||||||
|
{
|
||||||
|
'at': self.end_date.isoformat(),
|
||||||
|
'amout': 0,
|
||||||
|
'label': "actuel",
|
||||||
|
'balance': 0,
|
||||||
|
}
|
||||||
|
] + [
|
||||||
|
{
|
||||||
|
'at': op.at.isoformat(),
|
||||||
|
'amount': op.amount,
|
||||||
|
'label': str(op),
|
||||||
|
'balance': 0,
|
||||||
|
} for op in opgroups
|
||||||
|
] + [
|
||||||
|
{
|
||||||
|
'at': tr.group.at.isoformat(),
|
||||||
|
'amount': tr.amount,
|
||||||
|
'label': "%d€: %s -> %s" % (tr.amount,
|
||||||
|
tr.from_acc.trigramme,
|
||||||
|
tr.to_acc.trigramme),
|
||||||
|
'balance': 0,
|
||||||
|
} for tr in received_transfers
|
||||||
|
] + [
|
||||||
|
{
|
||||||
|
'at': tr.group.at.isoformat(),
|
||||||
|
'amount': -tr.amount,
|
||||||
|
'label': "%d€: %s -> %s" % (tr.amount,
|
||||||
|
tr.from_acc.trigramme,
|
||||||
|
tr.to_acc.trigramme),
|
||||||
|
'balance': 0,
|
||||||
|
} for tr in emitted_transfers
|
||||||
|
]
|
||||||
|
if not self.anytime:
|
||||||
|
actions += [
|
||||||
|
# Date de début :
|
||||||
|
{
|
||||||
|
'at': self.begin_date.isoformat(),
|
||||||
|
'amount': 0,
|
||||||
|
'label': "début",
|
||||||
|
'balance': 0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
# Maintenant on trie la liste des actions par ordre du plus récent
|
||||||
|
# an plus ancien et on met à jour la balance
|
||||||
|
actions = sorted(actions, key=lambda k: k['at'], reverse=True)
|
||||||
|
actions[0]['balance'] = account.balance
|
||||||
|
for i in range(len(actions)-1):
|
||||||
|
actions[i+1]['balance'] = actions[i]['balance'] \
|
||||||
|
- actions[i+1]['amount']
|
||||||
|
return actions
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {}
|
||||||
|
changes = self.get_changes_list()
|
||||||
|
context['changes'] = changes
|
||||||
|
context['chart_id'] = "%s_%s" % (self.id_prefix,
|
||||||
|
self.object.id)
|
||||||
|
context['min_date'] = changes[len(changes)-1]['at']
|
||||||
|
context['max_date'] = changes[0]['at']
|
||||||
|
# TODO: offset
|
||||||
|
return context
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
return super(AccountStatBalance, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# AccountStatLast
|
||||||
|
# ---------------
|
||||||
|
class AccountStatLast(HybridDetailView):
|
||||||
|
"""
|
||||||
|
Returns a graph (or a JSON Response) of the evolution a the personnal
|
||||||
|
consommation of a trigramm at the diffent dates precised
|
||||||
|
"""
|
||||||
|
model = Account
|
||||||
|
trigramme_url_kwarg = 'trigramme'
|
||||||
|
template_name = 'kfet/account_stat_last.html'
|
||||||
|
context_object_name = 'account'
|
||||||
|
end_date = timezone.now()
|
||||||
|
id_prefix = "lol"
|
||||||
|
|
||||||
|
# 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_object(self, **kwargs):
|
||||||
|
trigramme = self.kwargs.get(self.trigramme_url_kwarg)
|
||||||
|
return get_object_or_404(Account, trigramme=trigramme)
|
||||||
|
|
||||||
|
def sort_operations(self, **kwargs):
|
||||||
|
# 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(group__on_acc=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])
|
||||||
|
)
|
||||||
|
return operations
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
# On hérite
|
||||||
|
# en fait non, pas besoin et c'est chiant à dumper
|
||||||
|
# context = super(AccountStat, self).get_context_data(**kwargs)
|
||||||
|
context = {}
|
||||||
|
nb_ventes = {}
|
||||||
|
# On récupère les labels des dates
|
||||||
|
context['labels'] = self.get_labels().copy()
|
||||||
|
# On compte les opérations
|
||||||
|
operations = self.sort_operations()
|
||||||
|
for i in operations:
|
||||||
|
nb_ventes[i] = tot_ventes(operations[i])
|
||||||
|
context['nb_ventes'] = nb_ventes
|
||||||
|
# ID unique
|
||||||
|
context['chart_id'] = "%s_%d" % (self.id_prefix,
|
||||||
|
self.object.id)
|
||||||
|
return context
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
return super(AccountStatLast, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# ArticleStatLast
|
||||||
|
# ---------------
|
||||||
|
# Rend un graph des ventes sur une plage de temps à préciser.
|
||||||
|
# Le graphique distingue les ventes sur LIQ et sur les autres trigrammes
|
||||||
|
class ArticleStatLast(HybridDetailView):
|
||||||
|
"""
|
||||||
|
Returns a graph (or a JSON Response) of the consommation
|
||||||
|
of an article at the diffent dates precised
|
||||||
|
"""
|
||||||
|
model = Article
|
||||||
|
template_name = 'kfet/article_stat_last.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(ArticleStatLast, 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_%d" % (self.id_prefix,
|
||||||
|
self.object.id)
|
||||||
|
return context
|
||||||
|
|
||||||
|
@method_decorator(login_required)
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
return super(ArticleStatLast, self).dispatch(*args, **kwargs)
|
||||||
|
|
367
kfet/views.py
367
kfet/views.py
|
@ -8,8 +8,6 @@ from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError
|
from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.views.generic import ListView, DetailView
|
from django.views.generic import ListView, DetailView
|
||||||
from django.views.generic.list import BaseListView, MultipleObjectTemplateResponseMixin
|
|
||||||
from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin
|
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -41,7 +39,9 @@ import statistics
|
||||||
from .statistic import daynames, monthnames, weeknames, \
|
from .statistic import daynames, monthnames, weeknames, \
|
||||||
lastdays, lastweeks, lastmonths, \
|
lastdays, lastweeks, lastmonths, \
|
||||||
this_morning, this_monday_morning, this_first_month_day, \
|
this_morning, this_monday_morning, this_first_month_day, \
|
||||||
tot_ventes
|
tot_ventes, \
|
||||||
|
HybridDetailView, ObjectResumeStat, \
|
||||||
|
ArticleStatLast, AccountStatBalance, AccountStatLast
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def home(request):
|
def home(request):
|
||||||
|
@ -1957,103 +1957,6 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView):
|
||||||
# ==========
|
# ==========
|
||||||
# Statistics
|
# 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)
|
|
||||||
|
|
||||||
|
|
||||||
# Rend un ListView 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 HybridListView(JSONResponseMixin,
|
|
||||||
MultipleObjectTemplateResponseMixin,
|
|
||||||
BaseListView):
|
|
||||||
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(HybridListView, self).render_to_response(context)
|
|
||||||
|
|
||||||
|
|
||||||
# Un résume des toutes les vues de stat d'un objet
|
|
||||||
# NE REND PAS DE JSON
|
|
||||||
class ObjectResumeStat(DetailView):
|
|
||||||
template_name = 'kfet/object_stat_resume.html'
|
|
||||||
context_object_name = 'lul'
|
|
||||||
id_prefix = 'id_a_definir'
|
|
||||||
# nombre de vues à résumer
|
|
||||||
nb_stat = 2
|
|
||||||
# Le combienième est celui par defaut ?
|
|
||||||
# (entre 0 et nb_stat-1)
|
|
||||||
nb_default = 0
|
|
||||||
stat_labels = ['stat_1', 'stat_2']
|
|
||||||
stat_urls = ['url_1', 'url_2']
|
|
||||||
|
|
||||||
# sert à renverser les urls
|
|
||||||
# utile de le surcharger quand l'url prend d'autres arguments que l'id
|
|
||||||
def get_object_url_kwargs(self, **kwargs):
|
|
||||||
return {'pk': self.object.id}
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
# On hérite pas
|
|
||||||
object_id = self.object.id
|
|
||||||
context = {}
|
|
||||||
stats = {}
|
|
||||||
for i in range(self.nb_stat):
|
|
||||||
stats[i] = {
|
|
||||||
'label': self.stat_labels[i],
|
|
||||||
'btn': "btn_%s_%d_%d" % (self.id_prefix,
|
|
||||||
object_id,
|
|
||||||
i),
|
|
||||||
'url': reverse_lazy(self.stat_urls[i],
|
|
||||||
kwargs=self.get_object_url_kwargs()),
|
|
||||||
}
|
|
||||||
prefix = "%s_%d" % (self.id_prefix, object_id)
|
|
||||||
context['id_prefix'] = prefix
|
|
||||||
context['content_id'] = "content_%s" % prefix
|
|
||||||
context['stats'] = stats
|
|
||||||
context['default_stat'] = self.nb_default
|
|
||||||
context['object_id'] = object_id
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------
|
# -----------------------
|
||||||
# Evolution Balance perso
|
# Evolution Balance perso
|
||||||
# -----------------------
|
# -----------------------
|
||||||
|
@ -2093,122 +1996,6 @@ class AccountStatBalanceAll(ObjectResumeStat):
|
||||||
return super(AccountStatBalanceAll, self).dispatch(*args, **kwargs)
|
return super(AccountStatBalanceAll, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Rend un graphe (ou un json) de l'évolution de la balance personelle
|
|
||||||
# entre begin_date et end_date
|
|
||||||
# prend en compte les opérations et les transferts
|
|
||||||
# ne prend pas en compte les balance offset (TODO?)
|
|
||||||
class AccountStatBalance(HybridDetailView):
|
|
||||||
model = Account
|
|
||||||
trigramme_url_kwarg = 'trigramme'
|
|
||||||
template_name = 'kfet/account_stat_balance.html'
|
|
||||||
context_object_name = 'account'
|
|
||||||
begin_date = this_morning()
|
|
||||||
end_date = timezone.now() # ne gère pas encore autre chose que now
|
|
||||||
anytime = False # un cas particulier
|
|
||||||
id_prefix = "lol"
|
|
||||||
|
|
||||||
def get_object(self, **kwargs):
|
|
||||||
trigramme = self.kwargs.get(self.trigramme_url_kwarg)
|
|
||||||
return get_object_or_404(Account, trigramme=trigramme)
|
|
||||||
|
|
||||||
def get_changes_list(self, **kwargs):
|
|
||||||
account = self.object
|
|
||||||
# On récupère les opérations
|
|
||||||
# TODO: retirer les opgroup dont tous les op sont annulées
|
|
||||||
opgroups = list(OperationGroup.objects
|
|
||||||
.filter(on_acc=account)
|
|
||||||
.filter(at__gte=self.begin_date)
|
|
||||||
.filter(at__lte=self.end_date))
|
|
||||||
# On récupère les transferts reçus
|
|
||||||
received_transfers = list(Transfer.objects
|
|
||||||
.filter(to_acc=account)
|
|
||||||
.filter(canceled_at=None)
|
|
||||||
.filter(group__at__gte=self.begin_date)
|
|
||||||
.filter(group__at__lte=self.end_date))
|
|
||||||
# On récupère les transferts émis
|
|
||||||
emitted_transfers = list(Transfer.objects
|
|
||||||
.filter(from_acc=account)
|
|
||||||
.filter(canceled_at=None)
|
|
||||||
.filter(group__at__gte=self.begin_date)
|
|
||||||
.filter(group__at__lte=self.end_date))
|
|
||||||
# On transforme tout ça en une liste de dictionnaires sous la forme
|
|
||||||
# {'at': date,
|
|
||||||
# 'amount': changement de la balance (négatif si diminue la balance,
|
|
||||||
# positif si l'augmente),
|
|
||||||
# 'label': text descriptif,
|
|
||||||
# 'balance': état de la balance après l'action (0 pour le moment,
|
|
||||||
# sera mis à jour lors d'une
|
|
||||||
# autre passe)
|
|
||||||
# }
|
|
||||||
actions = [
|
|
||||||
# Maintenant (à changer si on gère autre chose que now)
|
|
||||||
{
|
|
||||||
'at': self.end_date.isoformat(),
|
|
||||||
'amout': 0,
|
|
||||||
'label': "actuel",
|
|
||||||
'balance': 0,
|
|
||||||
}
|
|
||||||
] + [
|
|
||||||
{
|
|
||||||
'at': op.at.isoformat(),
|
|
||||||
'amount': op.amount,
|
|
||||||
'label': str(op),
|
|
||||||
'balance': 0,
|
|
||||||
} for op in opgroups
|
|
||||||
] + [
|
|
||||||
{
|
|
||||||
'at': tr.group.at.isoformat(),
|
|
||||||
'amount': tr.amount,
|
|
||||||
'label': "%d€: %s -> %s" % (tr.amount,
|
|
||||||
tr.from_acc.trigramme,
|
|
||||||
tr.to_acc.trigramme),
|
|
||||||
'balance': 0,
|
|
||||||
} for tr in received_transfers
|
|
||||||
] + [
|
|
||||||
{
|
|
||||||
'at': tr.group.at.isoformat(),
|
|
||||||
'amount': -tr.amount,
|
|
||||||
'label': "%d€: %s -> %s" % (tr.amount,
|
|
||||||
tr.from_acc.trigramme,
|
|
||||||
tr.to_acc.trigramme),
|
|
||||||
'balance': 0,
|
|
||||||
} for tr in emitted_transfers
|
|
||||||
]
|
|
||||||
if not self.anytime:
|
|
||||||
actions += [
|
|
||||||
# Date de début :
|
|
||||||
{
|
|
||||||
'at': self.begin_date.isoformat(),
|
|
||||||
'amount': 0,
|
|
||||||
'label': "début",
|
|
||||||
'balance': 0,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
# Maintenant on trie la liste des actions par ordre du plus récent
|
|
||||||
# an plus ancien et on met à jour la balance
|
|
||||||
actions = sorted(actions, key=lambda k: k['at'], reverse=True)
|
|
||||||
actions[0]['balance'] = account.balance
|
|
||||||
for i in range(len(actions)-1):
|
|
||||||
actions[i+1]['balance'] = actions[i]['balance'] \
|
|
||||||
- actions[i+1]['amount']
|
|
||||||
return actions
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = {}
|
|
||||||
changes = self.get_changes_list()
|
|
||||||
context['changes'] = changes
|
|
||||||
context['chart_id'] = "%s_%s" % (self.id_prefix,
|
|
||||||
self.object.id)
|
|
||||||
context['min_date'] = changes[len(changes)-1]['at']
|
|
||||||
context['max_date'] = changes[0]['at']
|
|
||||||
# TODO: offset
|
|
||||||
return context
|
|
||||||
|
|
||||||
@method_decorator(login_required)
|
|
||||||
def dispatch(self, *args, **kwargs):
|
|
||||||
return super(AccountStatBalance, self).dispatch(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Rend l'évolution de la balance perso de ces 30 derniers jours
|
# Rend l'évolution de la balance perso de ces 30 derniers jours
|
||||||
class AccountStatBalanceMonth(AccountStatBalance):
|
class AccountStatBalanceMonth(AccountStatBalance):
|
||||||
begin_date = this_morning() - timezone.timedelta(days=30)
|
begin_date = this_morning() - timezone.timedelta(days=30)
|
||||||
|
@ -2275,73 +2062,6 @@ class AccountStatLastAll(ObjectResumeStat):
|
||||||
return super(AccountStatLastAll, self).dispatch(*args, **kwargs)
|
return super(AccountStatLastAll, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AccountStatLast(HybridDetailView):
|
|
||||||
model = Account
|
|
||||||
trigramme_url_kwarg = 'trigramme'
|
|
||||||
template_name = 'kfet/account_stat_last.html'
|
|
||||||
context_object_name = 'account'
|
|
||||||
end_date = timezone.now()
|
|
||||||
id_prefix = "lol"
|
|
||||||
|
|
||||||
# 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_object(self, **kwargs):
|
|
||||||
trigramme = self.kwargs.get(self.trigramme_url_kwarg)
|
|
||||||
return get_object_or_404(Account, trigramme=trigramme)
|
|
||||||
|
|
||||||
def sort_operations(self, **kwargs):
|
|
||||||
# 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(group__on_acc=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])
|
|
||||||
)
|
|
||||||
return operations
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
# On hérite
|
|
||||||
# en fait non, pas besoin et c'est chiant à dumper
|
|
||||||
# context = super(AccountStat, self).get_context_data(**kwargs)
|
|
||||||
context = {}
|
|
||||||
nb_ventes = {}
|
|
||||||
# On récupère les labels des dates
|
|
||||||
context['labels'] = self.get_labels().copy()
|
|
||||||
# On compte les opérations
|
|
||||||
operations = self.sort_operations()
|
|
||||||
for i in operations:
|
|
||||||
nb_ventes[i] = tot_ventes(operations[i])
|
|
||||||
context['nb_ventes'] = nb_ventes
|
|
||||||
# ID unique
|
|
||||||
context['chart_id'] = "%s_%d" % (self.id_prefix,
|
|
||||||
self.object.id)
|
|
||||||
return context
|
|
||||||
|
|
||||||
@method_decorator(login_required)
|
|
||||||
def dispatch(self, *args, **kwargs):
|
|
||||||
return super(AccountStatLast, self).dispatch(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Rend les achats pour ce compte des 7 derniers jours
|
# Rend les achats pour ce compte des 7 derniers jours
|
||||||
|
@ -2413,87 +2133,6 @@ class ArticleStatLastAll(ObjectResumeStat):
|
||||||
return super(ArticleStatLastAll, self).dispatch(*args, **kwargs)
|
return super(ArticleStatLastAll, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Rend un graph des ventes sur une plage de temps à préciser.
|
|
||||||
# Le graphique distingue les ventes sur LIQ et sur les autres trigrammes
|
|
||||||
class ArticleStatLast(HybridDetailView):
|
|
||||||
model = Article
|
|
||||||
template_name = 'kfet/article_stat_last.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(ArticleStatLast, 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_%d" % (self.id_prefix,
|
|
||||||
self.object.id)
|
|
||||||
return context
|
|
||||||
|
|
||||||
@method_decorator(login_required)
|
|
||||||
def dispatch(self, *args, **kwargs):
|
|
||||||
return super(ArticleStatLast, self).dispatch(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Rend les ventes des 7 derniers jours
|
# Rend les ventes des 7 derniers jours
|
||||||
# Aujourd'hui non compris
|
# Aujourd'hui non compris
|
||||||
class ArticleStatLastDay(ArticleStatLast):
|
class ArticleStatLastDay(ArticleStatLast):
|
||||||
|
|
Loading…
Reference in a new issue