cleaning: PEP8, html, permissions

This commit is contained in:
Qwann 2016-12-24 12:33:04 +01:00
parent 7070129add
commit de9387c6ad
9 changed files with 231 additions and 78 deletions

View file

@ -505,6 +505,10 @@ class OperationGroup(models.Model):
related_name = "+",
blank = True, null = True, default = None)
def __str__(self):
return ', '.join(map(str, self.opes.all()))
class Operation(models.Model):
PURCHASE = 'purchase'
DEPOSIT = 'deposit'
@ -549,6 +553,18 @@ class Operation(models.Model):
max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None)
def __str__(self):
templates = {
self.PURCHASE: "{nb} {article.name} ({amount}€)",
self.DEPOSIT: "charge ({amount})",
self.WITHDRAW: "retrait ({amount})",
self.INITIAL: "initial ({amount})",
}
return templates[self.type].format(nb=self.article_nb,
article=self.article,
amount=self.amount)
class GlobalPermissions(models.Model):
class Meta:
managed = False

View file

@ -364,3 +364,11 @@ textarea {
.help h4 {
margin:15px 0;
}
/*
* Statistiques
*/
.stat-graph {
height: 100px;
}

View file

@ -15,8 +15,11 @@
<script>
jQuery(document).ready(function() {
var stat_last = $("#stat_last");
var stat_balance = $("#stat_balance");
var stat_last_url = "{% url 'kfet.account.stat.last' trigramme=account.trigramme %}";
var stat_balance_url = "{% url 'kfet.account.stat.balance' trigramme=account.trigramme %}";
get_thing(stat_last_url, stat_last, "Stat non trouvées :(");
get_thing(stat_balance_url, stat_balance, "Stat non trouvées :(");
// FONCTIONS
// Permet de raffraichir un champ, étant donné :
// thing_url : l'url contenant le contenu
@ -76,13 +79,21 @@ jQuery(document).ready(function() {
</div>
{% endif %}
{% if account.user == request.user %}
<div class="content-right-block">
<div class="content-right-block content-right-block-transparent">
<h2>Statistiques</h2>
<div class="row">
<div class="col-sm-12 col-md-6 nopadding">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ma balance</h3>
<div id="stat_balance" class"stat-graph"></div>
</div>
</div>
</div><!-- /row -->
<div class="row">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ma consommation</h3>
<div id="stat_last"></div>
<div id="stat_last" class"stat-graph"></div>
</div>
</div>
</div><!-- /row -->

View file

@ -1,7 +1,20 @@
<!doctype html>
{% load dictionary_extras %}
<body>
<canvas id="{{ chart_id }}"></canvas>
<canvas id="{{ chart_id }}" height="100"></canvas>
{% comment %}
<ul>
{% for change in changes %}
<li>
{{ change | get_item:'label'}}
| {{ change | get_item:'at'}}
| ({{ change | get_item:'amount'}})
| balance {{ change | get_item:'balance'}}
</li>
{% endfor %}
</ul>
{% endcomment %}
<script>
jQuery(document).ready(function() {
@ -9,29 +22,21 @@ jQuery(document).ready(function() {
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: 'Nb items achetés',
label: 'Balance',
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
data: [
{% for k,nb in nb_ventes.items %}
{% if forloop.last %}
"{{ nb }}"
{% else %}
"{{ nb }}",
{% endif %}
{% endfor %}
{% for change in changes %}
{
x: new Date("{{ change | get_item:'at'}}"),
y: {{ change | get_item:'balance'| stringformat:"f" }},
label: "{{change|get_item:'label'}}"
}{% if not forloop.last %}, {% endif %}
{% endfor %}
],
fill: false,
fill: true,
steppedLine: true,
lineTension: 0,
}]
},
@ -45,6 +50,40 @@ jQuery(document).ready(function() {
mode: 'nearest',
intersect: false,
},
scales: {
xAxes: [{
type: "time",
display: true,
scaleLabel: {
display: false,
labelString: 'Date'
},
time: {
tooltipFormat: 'll HH:mm',
min: new Date("{{ min_date }}"),
max: new Date("{{ max_date }}"),
displayFormats: {
'millisecond': 'SSS [ms]',
'second': 'mm:ss a',
'minute': 'DD MMM',
'hour': 'ddd h[h]',
'day': 'DD MMM',
'week': 'DD MMM',
'month': 'MMM',
'quarter': 'MMM',
'year': 'YYYY',
}
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: false,
labelString: 'value'
}
}]
}
}
});
});

View file

@ -1,7 +1,7 @@
<!doctype html>
<body>
<canvas id="{{ chart_id }}"></canvas>
<canvas id="{{ chart_id }}" height="100" ></canvas>
<script>
jQuery(document).ready(function() {

View file

@ -78,9 +78,11 @@
</table>
</div>
</div><!-- /row-->
</div>
<div class="content-right-block content-right-block-transparent">
<h2>Statistiques</h2>
<div class="row">
<div class="col-sm-12 col-md-6 nopadding">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ventes de {{ article.name }}</h3>
<div id="stat_last"></div>

View file

@ -1,7 +1,7 @@
<!doctype html>
<body>
<canvas id="{{ chart_id }}"></canvas>
<canvas id="{{ chart_id }}" height="100" ></canvas>
<script>
jQuery(document).ready(function() {

View file

@ -5,27 +5,47 @@
<div class="btn-group btn-group-justified" role="group" aria-label="select-period">
{% for k,stat in stats.items %}
<div class="btn-group" role="group">
<button id="{{ stat | get_item:'btn' }}" type="button" class="btn btn-primary">{{ stat | get_item:'label' }}</button>
<button id="{{ stat | get_item:'btn' }}" type="button" class="btn btn-primary {{ id_prefix }}-btn {%if k == default_stat%} focus{%endif%}">{{ stat | get_item:'label' }}</button>
</div>
{% endfor %}
</div><!-- /boutons -->
<div id="{{ content_id}}">
</div>
<script>
jQuery(document).ready(function() {
// FONCTIONS
// Permet de raffraichir un champ, étant donné :
// thing_url : l'url contenant le contenu
// thing_div : le div où le mettre
// empty_... : le truc à dire si on a un contenu vide
function get_thing(thing_url, thing_div, empty_thing_message) {
$.get(thing_url, function(data) {
if(jQuery.trim(data).length==0) {
thing_div.html(empty_thing_message);
} else {
thing_div.html(data);
}
});
}
});
</script>
<script>
jQuery(document).ready(function() {
// VARIABLES
// défaut
{{if_prefix}}_content_id = $("#{{content_id}}");
{{id_prefix}}_content_id = $("#{{content_id}}");
{{id_prefix}}_btns = $(".{{id_prefix}}-btn");
{% for k,stat in stats.items %}
{% if k == default_stat %}
{{id_prefix}}_default_url = "{{ stat | get_item:'url' }}";
{% endif %}
{% endfor %}
// INIT
get_thing({{id_prefix}}_default_url, {{if_prefix}}_content_id, "Ouppss ?");
get_thing({{id_prefix}}_default_url, {{id_prefix}}_content_id, "Ouppss ?");
{% for k,stat in stats.items %}
$("#{{stat|get_item:'btn'}}").on('click', function() {
get_thing("{{stat|get_item:'url'}}", {{if_prefix}}_content_id, "Ouuups ?")
get_thing("{{stat|get_item:'url'}}", {{id_prefix}}_content_id, "Ouuups ?");
{{id_prefix}}_btns.removeClass("focus");
$("#{{stat|get_item:'btn'}}").addClass("focus");
});
{% endfor %}
// FONCTIONS

View file

@ -24,11 +24,12 @@ 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 django.utils.decorators import method_decorator
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, Operation)
InventoryArticle, Order, OrderArticle, Operation, OperationGroup, Transfer)
from kfet.forms import *
from collections import defaultdict
from kfet import consumers
@ -2087,18 +2088,23 @@ class AccountStatBalanceAll(ObjectResumeStat):
def get_object_url_kwargs(self, **kwargs):
return {'trigramme': self.object.trigramme}
@method_decorator(login_required)
def dispatch(self, *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 autorisations de négatif (TODO?)
# 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()
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):
@ -2108,25 +2114,23 @@ class AccountStatBalance(HybridDetailView):
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)
)
.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(transfers__at__gte=self.begin_date)
.filter(transfers__at__lte=self.end_date)
)
.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(to_acc=account)
.filter(from_acc=account)
.filter(canceled_at=None)
.filter(transfers__at__gte=self.begin_date)
.filter(transfers__at__lte=self.end_date)
)
.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,
@ -2136,46 +2140,74 @@ class AccountStatBalance(HybridDetailView):
# sera mis à jour lors d'une
# autre passe)
# }
actions=[]
for op in opgroups:
action = {
'at': op.at,
'amount': op.amount,
'label': "opération", #TODO
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,
}
actions.append(action)
for tr in received_transfers:
action = {
'at': tr.transfers.at,
'amount': re.amount,
'label': "Transfert", #TODO
'balance': 0,
}
actions.append(action)
for tr in emitted_transfers:
action = {
'at': tr.transfers.at,
'amount': -re.amount,
'label': "Transfert", #TODO
'balance': 0,
}
actions.append(action)
}
]
# 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']
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
class AccountStatBalanceMonth(AccountStatBalance):
@ -2205,10 +2237,11 @@ class AccountStatBalanceYear(AccountStatBalance):
class AccountStatBalanceAnytime(AccountStatBalance):
begin_date = timezone.datetime(year=1980, month=1, day=1)
id_prefix = ID_PREFIX_ACC_BALANCE_ANYTIME
anytime = True
# ------------------------
# Consommation personnelle
# Consommation personnelle
# ------------------------
ID_PREFIX_ACC_LAST = "last_acc"
ID_PREFIX_ACC_LAST_DAYS = "last_days_acc"
@ -2237,6 +2270,10 @@ class AccountStatLastAll(ObjectResumeStat):
def get_object_url_kwargs(self, **kwargs):
return {'trigramme': self.object.trigramme}
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(AccountStatLastAll, self).dispatch(*args, **kwargs)
class AccountStatLast(HybridDetailView):
model = Account
@ -2302,6 +2339,10 @@ class AccountStatLast(HybridDetailView):
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
# Aujourd'hui non compris
@ -2367,6 +2408,10 @@ class ArticleStatLastAll(ObjectResumeStat):
'kfet.article.stat.last.week',
'kfet.article.stat.last.day']
@method_decorator(login_required)
def dispatch(self, *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
@ -2444,6 +2489,10 @@ class ArticleStatLast(HybridDetailView):
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
# Aujourd'hui non compris
@ -2472,6 +2521,7 @@ class ArticleStatLastWeek(ArticleStatLast):
weeks = lastweeks(7)
return weeknames(weeks)
# Rend les ventes des 7 derniers mois
# Le mois en cours n'est pas compris
class ArticleStatLastMonth(ArticleStatLast):
@ -2489,9 +2539,10 @@ class ArticleStatLastMonth(ArticleStatLast):
# Article Statistique Catégories
# ------------------------------
class DurationStat(HybridListView):
lookup_duration_type = 'day' # 'day' || 'week' || 'month'
lookup_duration_number = 3 # ie ici : 3 jours
lookup_duration_type = 'day' # 'day' || 'week' || 'month'
lookup_duration_number = 3 # ie ici : 3 jours
def get_end_date(self, **kwargs):
if self.lookup_duration_type == 'day':
@ -2500,7 +2551,8 @@ class DurationStat(HybridListView):
return this_monday_morning()
elif self.lookup_duration_type == 'month':
return this_first_month_day()
else: raise ValueError('duration_type invalid')
else:
raise ValueError('duration_type invalid')
def get_begining_date(self, **kwargs):
end_date = self.get_end_date(self, **kwargs)
@ -2510,18 +2562,23 @@ class DurationStat(HybridListView):
days = 7*self.lookup_nb_duration
elif self.lookup_duration_type == 'month':
days = 30*self.lookup_nb_duration
else: raise ValueError('this should not be happening.')
else:
raise ValueError('this should not be happening.')
delta = timezone.timedelta(days=days)
return end_date - delta
#TODO
# class CategoryStatAll(DurationStat):
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(DurationStat, self).dispatch(*args, **kwargs)
# TODO
# class CategoryDurationStat(DurationStat):
# model = ArticleCategory
# template_name = 'kfet/category_stat.html'
#
#
# def get_context_data(self, **kwargs):
# context = {}
# queryset = kwargs.pop('object_list', self.object_list)
#
#
# return context