Merge branch 'k-fet' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/command_interface
This commit is contained in:
commit
61e2fedb08
17 changed files with 28734 additions and 86 deletions
|
@ -18,6 +18,7 @@ from django.db.models import F
|
|||
from django.core.cache import cache
|
||||
from datetime import date, timedelta
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
def choices_length(choices):
|
||||
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
||||
|
@ -154,6 +155,7 @@ class Account(models.Model):
|
|||
# - Enregistre User, CofProfile à partir de "data"
|
||||
# - Enregistre Account
|
||||
def save(self, data = {}, *args, **kwargs):
|
||||
|
||||
if self.pk and data:
|
||||
# Account update
|
||||
|
||||
|
@ -200,6 +202,11 @@ class Account(models.Model):
|
|||
self.cofprofile = cof
|
||||
super(Account, self).save(*args, **kwargs)
|
||||
|
||||
def change_pwd(self, pwd):
|
||||
pwd_sha256 = hashlib.sha256(pwd.encode('utf-8'))\
|
||||
.hexdigest()
|
||||
self.password = pwd_sha256
|
||||
|
||||
# Surcharge de delete
|
||||
# Pas de suppression possible
|
||||
# Cas à régler plus tard
|
||||
|
@ -505,6 +512,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 +560,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
|
||||
|
|
|
@ -99,6 +99,25 @@ textarea {
|
|||
font-weight:bold;
|
||||
}
|
||||
|
||||
.nopadding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.panel-md-margin{
|
||||
background-color: white;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
padding-bottom: 15px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.panel-md-margin{
|
||||
margin:8px;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.col-content-left, .col-content-right {
|
||||
padding:0;
|
||||
}
|
||||
|
@ -165,6 +184,10 @@ textarea {
|
|||
background:#fff;
|
||||
}
|
||||
|
||||
.content-right-block-transparent > div:not(.buttons-title) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.content-right-block .buttons-title {
|
||||
position:absolute;
|
||||
top:8px;
|
||||
|
@ -364,3 +387,11 @@ textarea {
|
|||
thead .tooltip {
|
||||
font-size:13px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Statistiques
|
||||
*/
|
||||
|
||||
.stat-graph {
|
||||
height: 100px;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
#account, #checkout, input, #history, #basket, #basket_rel, #previous_op, #articles_data {
|
||||
#account, #checkout, #article_selection, #history, #basket, #basket_rel, #previous_op, #articles_data {
|
||||
background:#fff;
|
||||
}
|
||||
|
||||
|
@ -252,7 +252,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
width:100%;
|
||||
}
|
||||
|
||||
#article_selection input {
|
||||
#article_selection input, #article_selection span {
|
||||
height:100%;
|
||||
float:left;
|
||||
border:0;
|
||||
|
@ -263,12 +263,12 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
font-weight:bold;
|
||||
}
|
||||
|
||||
#article_selection input+input {
|
||||
#article_selection input+input #article_selection input+span {
|
||||
border-right:0;
|
||||
}
|
||||
|
||||
#article_autocomplete {
|
||||
width:90%;
|
||||
width:80%;
|
||||
padding-left:10px;
|
||||
}
|
||||
|
||||
|
@ -277,14 +277,24 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
text-align:center;
|
||||
}
|
||||
|
||||
#article_stock {
|
||||
width:10%;
|
||||
line-height:38px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
@media (min-width:1200px) {
|
||||
#article_autocomplete {
|
||||
width:92%
|
||||
width:84%
|
||||
}
|
||||
|
||||
#article_number {
|
||||
width:8%;
|
||||
}
|
||||
|
||||
#article_stock {
|
||||
width:8%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Article data */
|
||||
|
@ -319,6 +329,10 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
padding-left:20px;
|
||||
}
|
||||
|
||||
#articles_data .article.low-stock {
|
||||
background:rgba(236,100,0,0.3);
|
||||
}
|
||||
|
||||
#articles_data .article:hover {
|
||||
background:rgba(200,16,46,0.3);
|
||||
cursor:pointer;
|
||||
|
@ -384,6 +398,11 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
text-align:right;
|
||||
}
|
||||
|
||||
#basket tr .lowstock {
|
||||
display:none;
|
||||
padding-right:15px;
|
||||
}
|
||||
|
||||
#basket tr.ui-selected, #basket tr.ui-selecting {
|
||||
background-color:rgba(200,16,46,0.6);
|
||||
color:#FFF;
|
||||
|
|
15855
kfet/static/kfet/js/Chart.bundle.js
Normal file
15855
kfet/static/kfet/js/Chart.bundle.js
Normal file
File diff suppressed because it is too large
Load diff
16
kfet/static/kfet/js/Chart.bundle.min.js
vendored
Normal file
16
kfet/static/kfet/js/Chart.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11557
kfet/static/kfet/js/Chart.js
vendored
Normal file
11557
kfet/static/kfet/js/Chart.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -37,9 +37,10 @@ function amountDisplay(amount, is_cof=false, tri='') {
|
|||
return amountToUKF(amount, is_cof);
|
||||
}
|
||||
|
||||
function amountToUKF(amount, is_cof=false) {
|
||||
function amountToUKF(amount, is_cof=false, account=false) {
|
||||
var rounding = account ? Math.floor : Math.round ;
|
||||
var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1;
|
||||
return Math.floor(amount * coef_cof * 10);
|
||||
return rounding(amount * coef_cof * 10);
|
||||
}
|
||||
|
||||
function isValidTrigramme(trigramme) {
|
||||
|
|
197
kfet/static/kfet/js/statistic.js
Normal file
197
kfet/static/kfet/js/statistic.js
Normal file
|
@ -0,0 +1,197 @@
|
|||
(function($){
|
||||
window.StatsGroup = function (url, target) {
|
||||
// a class to properly display statictics
|
||||
|
||||
// url : points to an ObjectResumeStat that lists the options through JSON
|
||||
// target : element of the DOM where to put the stats
|
||||
|
||||
var self = this;
|
||||
var element = $(target);
|
||||
var content = $("<div>");
|
||||
var buttons;
|
||||
|
||||
function dictToArray (dict, start) {
|
||||
// converts the dicts returned by JSONResponse to Arrays
|
||||
// necessary because for..in does not guarantee the order
|
||||
if (start === undefined) start = 0;
|
||||
var array = new Array();
|
||||
for (var k in dict) {
|
||||
array[k] = dict[k];
|
||||
}
|
||||
array.splice(0, start);
|
||||
return array;
|
||||
}
|
||||
|
||||
function handleTimeChart (dict) {
|
||||
// reads the balance data and put it into chartjs formatting
|
||||
var data = dictToArray(dict, 0);
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var source = data[i];
|
||||
data[i] = { x: new Date(source.at),
|
||||
y: source.balance,
|
||||
label: source.label }
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function showStats () {
|
||||
// CALLBACK : called when a button is selected
|
||||
|
||||
// shows the focus on the correct button
|
||||
buttons.find(".focus").removeClass("focus");
|
||||
$(this).addClass("focus");
|
||||
|
||||
// loads data and shows it
|
||||
$.getJSON(this.stats_target_url + "?format=json",
|
||||
displayStats);
|
||||
}
|
||||
|
||||
function displayStats (data) {
|
||||
// reads the json data and updates the chart display
|
||||
|
||||
var chart_datasets = [];
|
||||
var charts = dictToArray(data.charts);
|
||||
|
||||
// are the points indexed by timestamps?
|
||||
var is_time_chart = data.is_time_chart || false;
|
||||
|
||||
// reads the charts data
|
||||
for (var i = 0; i < charts.length; i++) {
|
||||
var chart = charts[i];
|
||||
|
||||
// format the data
|
||||
var chart_data = is_time_chart ? handleTimeChart(chart.values) : dictToArray(chart.values, 1);
|
||||
|
||||
chart_datasets.push(
|
||||
{
|
||||
label: chart.label,
|
||||
borderColor: chart.color,
|
||||
backgroundColor: chart.color,
|
||||
fill: is_time_chart,
|
||||
lineTension: 0,
|
||||
data: chart_data,
|
||||
steppedLine: is_time_chart,
|
||||
});
|
||||
}
|
||||
|
||||
// options for chartjs
|
||||
var chart_options =
|
||||
{
|
||||
responsive: true,
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'nearest',
|
||||
intersect: false,
|
||||
}
|
||||
};
|
||||
|
||||
// additionnal options for time-indexed charts
|
||||
if (is_time_chart) {
|
||||
chart_options['scales'] = {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
display: false,
|
||||
labelString: 'Date'
|
||||
},
|
||||
time: {
|
||||
tooltipFormat: 'll HH:mm',
|
||||
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'
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// global object for the options
|
||||
var chart_model =
|
||||
{
|
||||
type: 'line',
|
||||
options: chart_options,
|
||||
data: {
|
||||
labels: dictToArray(data.labels, 1),
|
||||
datasets: chart_datasets,
|
||||
}
|
||||
};
|
||||
|
||||
// saves the previous charts to be destroyed
|
||||
var prev_chart = content.children();
|
||||
|
||||
// creates a blank canvas element and attach it to the DOM
|
||||
var canvas = $("<canvas>");
|
||||
content.append(canvas);
|
||||
|
||||
// create the chart
|
||||
var chart = new Chart(canvas, chart_model);
|
||||
|
||||
// clean
|
||||
prev_chart.remove();
|
||||
}
|
||||
|
||||
// initialize the interface
|
||||
function initialize (data) {
|
||||
// creates the bar with the buttons
|
||||
buttons = $("<div>",
|
||||
{class: "btn-group btn-group-justified",
|
||||
role: "group",
|
||||
"aria-label": "select-period"});
|
||||
|
||||
var to_click;
|
||||
var context = dictToArray(data.stats);
|
||||
|
||||
for (var i = 0; i < context.length; i++) {
|
||||
// creates the button
|
||||
var btn_wrapper = $("<div>",
|
||||
{class: "btn-group",
|
||||
role:"group"});
|
||||
var btn = $("<button>",
|
||||
{class: "btn btn-primary",
|
||||
type: "button"})
|
||||
.text(context[i].label)
|
||||
.prop("stats_target_url", context[i].url)
|
||||
.on("click", showStats);
|
||||
|
||||
// saves the default option to select
|
||||
if (i == data.default_stat || i == 0)
|
||||
to_click = btn;
|
||||
|
||||
// append the elements to the parent
|
||||
btn_wrapper.append(btn);
|
||||
buttons.append(btn_wrapper);
|
||||
}
|
||||
|
||||
// appends the contents to the DOM
|
||||
element.append(buttons);
|
||||
element.append(content);
|
||||
|
||||
// shows the default chart
|
||||
to_click.click();
|
||||
};
|
||||
|
||||
// constructor
|
||||
(function () {
|
||||
$.getJSON(url + "?format=json", initialize);
|
||||
})();
|
||||
};
|
||||
})(jQuery);
|
102
kfet/statistic.py
Normal file
102
kfet/statistic.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.utils import timezone
|
||||
from django.db.models import Sum
|
||||
|
||||
KFET_WAKES_UP_AT = 7
|
||||
|
||||
# donne le nom des jours d'une liste de dates
|
||||
# dans un dico ordonné
|
||||
def daynames(dates):
|
||||
names = {}
|
||||
for i in dates:
|
||||
names[i] = dates[i].strftime("%A")
|
||||
return names
|
||||
|
||||
|
||||
# donne le nom des semaines une liste de dates
|
||||
# dans un dico ordonné
|
||||
def weeknames(dates):
|
||||
names = {}
|
||||
for i in dates:
|
||||
names[i] = dates[i].strftime("Semaine %W")
|
||||
return names
|
||||
|
||||
|
||||
# donne le nom des mois d'une liste de dates
|
||||
# dans un dico ordonné
|
||||
def monthnames(dates):
|
||||
names = {}
|
||||
for i in dates:
|
||||
names[i] = dates[i].strftime("%B")
|
||||
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()
|
||||
first_days = {}
|
||||
this_year = first_month_day.year
|
||||
this_month = first_month_day.month
|
||||
for i in range(1, nb+1):
|
||||
month = ((this_month - 1 - (nb - i)) % 12) + 1
|
||||
year = this_year + (nb - i) // 12
|
||||
first_days[i] = timezone.datetime(year=year,
|
||||
month=month,
|
||||
day=1,
|
||||
hour=KFET_WAKES_UP_AT)
|
||||
return first_days
|
||||
|
||||
|
||||
def this_first_month_day():
|
||||
now = timezone.now()
|
||||
first_day = timezone.datetime(year=now.year,
|
||||
month=now.month,
|
||||
day=1,
|
||||
hour=KFET_WAKES_UP_AT)
|
||||
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,
|
||||
hour=KFET_WAKES_UP_AT)
|
||||
return monday_morning
|
||||
|
||||
|
||||
def this_morning():
|
||||
now = timezone.now()
|
||||
morning = timezone.datetime(year=now.year,
|
||||
month=now.month,
|
||||
day=now.day,
|
||||
hour=KFET_WAKES_UP_AT)
|
||||
return morning
|
||||
|
||||
|
||||
# Étant donné un queryset d'operations
|
||||
# rend la somme des article_nb
|
||||
def tot_ventes(queryset):
|
||||
res = queryset.aggregate(Sum('article_nb'))['article_nb__sum']
|
||||
return res and res or 0
|
|
@ -10,6 +10,18 @@
|
|||
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||
{% if account.user == request.user %}
|
||||
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
|
||||
<script>
|
||||
jQuery(document).ready(function() {
|
||||
var stat_last = new StatsGroup("{% url 'kfet.account.stat.last' trigramme=account.trigramme %}",
|
||||
$("#stat_last"));
|
||||
var stat_balance = new StatsGroup("{% url 'kfet.account.stat.balance' trigramme=account.trigramme %}",
|
||||
$("#stat_balance"));
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
|
@ -51,6 +63,27 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if account.user == request.user %}
|
||||
<div class="content-right-block content-right-block-transparent">
|
||||
<h2>Statistiques</h2>
|
||||
<div class="row">
|
||||
<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" class"stat-graph"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /row -->
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="content-right-block">
|
||||
<h2>Historique</h2>
|
||||
<div id="history">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Informations sur l'article {{ article }}{% endblock %}
|
||||
{% block content-header-title %}Article - {{ article.name }}{% endblock %}
|
||||
|
@ -76,10 +77,32 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /row-->
|
||||
</div>
|
||||
<div class="content-right-block content-right-block-transparent">
|
||||
<h2>Statistiques</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 nopadding">
|
||||
<div class="panel-md-margin">
|
||||
<h3>Ventes de {{ article.name }}</h3>
|
||||
<div id="stat_last"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /row -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
|
||||
<script>
|
||||
jQuery(document).ready(function() {
|
||||
var stat_last = $("#stat_last");
|
||||
var stat_last_url = "{% url 'kfet.article.stat.last' article.id %}";
|
||||
STAT.get_thing(stat_last_url, stat_last, "Stat non trouvées :(");
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
17
kfet/templates/kfet/home.html
Normal file
17
kfet/templates/kfet/home.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Accueil{% endblock %}
|
||||
{% block content-header-title %}Accueil{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -123,6 +123,7 @@
|
|||
<div id="article_selection">
|
||||
<input type="text" id="article_autocomplete" autocomplete="off">
|
||||
<input type="number" id="article_number" step="1" min="1">
|
||||
<span type="stock" id="article_stock"></span>
|
||||
<input type="hidden" id="article_id" value="">
|
||||
</div>
|
||||
<div id="articles_data">
|
||||
|
@ -221,7 +222,7 @@ $(document).ready(function() {
|
|||
function displayAccountData() {
|
||||
var balance = account_data['trigramme'] != 'LIQ' ? account_data['balance'] : '';
|
||||
if (balance != '')
|
||||
balance = amountToUKF(account_data['balance'], account_data['is_cof']);
|
||||
balance = amountToUKF(account_data['balance'], account_data['is_cof'], true);
|
||||
var is_cof = account_data['trigramme'] ? account_data['is_cof'] : '';
|
||||
if (is_cof !== '')
|
||||
is_cof = is_cof ? '<b>COF</b>' : '<b>Non-COF</b>';
|
||||
|
@ -616,7 +617,10 @@ $(document).ready(function() {
|
|||
for (var elem in article) {
|
||||
article_html.find('.'+elem).text(article[elem])
|
||||
}
|
||||
article_html.find('.price').text(amountToUKF(article['price'], false));
|
||||
if (-5 <= article['stock'] && article['stock'] <= 5) {
|
||||
article_html.addClass('low-stock');
|
||||
}
|
||||
article_html.find('.price').text(amountToUKF(article['price'], false, false)+' UKF');
|
||||
var category_html = articles_container
|
||||
.find('#data-category-'+article['category_id']);
|
||||
if (category_html.length == 0) {
|
||||
|
@ -642,7 +646,7 @@ $(document).ready(function() {
|
|||
});
|
||||
$after.after(article_html);
|
||||
// Pour l'autocomplétion
|
||||
articlesList.push([article['name'],article['id'],article['category_id'],article['price']]);
|
||||
articlesList.push([article['name'],article['id'],article['category_id'],article['price'], article['stock']]);
|
||||
}
|
||||
|
||||
function getArticles() {
|
||||
|
@ -670,8 +674,9 @@ $(document).ready(function() {
|
|||
var articleSelect = $('#article_autocomplete');
|
||||
var articleId = $('#article_id');
|
||||
var articleNb = $('#article_number');
|
||||
// 8:Backspace|9:Tab|13:Enter|46:DEL|112-117:F1-6|119-123:F8-F12
|
||||
var normalKeys = /^(8|9|13|46|112|113|114|115|116|117|119|120|121|122|123)$/;
|
||||
var articleStock = $('#article_stock');
|
||||
// 8:Backspace|9:Tab|13:Enter|38-40:Arrows|46:DEL|112-117:F1-6|119-123:F8-F12
|
||||
var normalKeys = /^(8|9|13|37|38|39|40|46|112|113|114|115|116|117|119|120|121|122|123)$/;
|
||||
var articlesList = [];
|
||||
|
||||
function deleteNonMatching(array, str) {
|
||||
|
@ -727,6 +732,7 @@ $(document).ready(function() {
|
|||
if (commit) {
|
||||
articleId.val(articlesMatch[0][1]);
|
||||
articleSelect.val(articlesMatch[0][0]);
|
||||
articleStock.text('/'+articlesMatch[0][4]);
|
||||
displayMatchedArticles(articlesList);
|
||||
return true;
|
||||
}
|
||||
|
@ -773,10 +779,15 @@ $(document).ready(function() {
|
|||
return $article.find('.name').text();
|
||||
}
|
||||
|
||||
function getArticleStock($article) {
|
||||
return $article.find('.stock').text();
|
||||
}
|
||||
|
||||
// Sélection des articles à la souris/tactile
|
||||
articles_container.on('click', '.article', function() {
|
||||
articleId.val(getArticleId($(this)));
|
||||
articleSelect.val(getArticleName($(this)));
|
||||
articleStock.text('/'+getArticleStock($(this)));
|
||||
displayMatchedArticles(articlesList);
|
||||
goToArticleNb();
|
||||
});
|
||||
|
@ -790,6 +801,7 @@ $(document).ready(function() {
|
|||
addPurchase(articleId.val(), articleNb.val());
|
||||
articleSelect.val('');
|
||||
articleNb.val('');
|
||||
articleStock.text('');
|
||||
articleSelect.focus();
|
||||
displayMatchedArticles(articlesList);
|
||||
return false;
|
||||
|
@ -809,7 +821,7 @@ $(document).ready(function() {
|
|||
// Basket
|
||||
// -----
|
||||
|
||||
var item_basket_default_html = '<tr><td class="amount"></td><td class="number"></td><td class="name"></td></tr>';
|
||||
var item_basket_default_html = '<tr><td class="amount"></td><td class="number"></td><td ><span class="lowstock glyphicon glyphicon-alert"></span></td><td class="name"></td></tr>';
|
||||
var basket_container = $('#basket table');
|
||||
var arrowKeys = /^(37|38|39|40)$/;
|
||||
|
||||
|
@ -827,16 +839,39 @@ $(document).ready(function() {
|
|||
}
|
||||
|
||||
function addPurchase(id, nb) {
|
||||
var amount_euro = amountEuroPurchase(id, nb).toFixed(2);
|
||||
var index = addPurchaseToFormset(article_data[1], nb, amount_euro);
|
||||
article_basket_html = $(item_basket_default_html);
|
||||
article_basket_html
|
||||
.attr('data-opeindex', index)
|
||||
.find('.number').text(nb).end()
|
||||
.find('.name').text(article_data[0]).end()
|
||||
.find('.amount').text(amountToUKF(amount_euro, account_data['is_cof']));
|
||||
basket_container.prepend(article_basket_html);
|
||||
updateBasketRel();
|
||||
var existing = false;
|
||||
formset_container.find('[data-opeindex]').each(function () {
|
||||
var opeindex = $(this).attr('data-opeindex');
|
||||
var article_id = $(this).find('#id_form-'+opeindex+'-article').val();
|
||||
if (article_id == id) {
|
||||
existing = true ;
|
||||
addExistingPurchase(opeindex, nb);
|
||||
}
|
||||
});
|
||||
if (!existing) {
|
||||
var amount_euro = amountEuroPurchase(id, nb).toFixed(2);
|
||||
var index = addPurchaseToFormset(article_data[1], nb, amount_euro);
|
||||
article_basket_html = $(item_basket_default_html);
|
||||
article_basket_html
|
||||
.attr('data-opeindex', index)
|
||||
.find('.number').text('('+nb+'/'+article_data[4]+')').end()
|
||||
.find('.name').text(article_data[0]).end()
|
||||
.find('.amount').text(amountToUKF(amount_euro, account_data['is_cof']), false);
|
||||
basket_container.prepend(article_basket_html);
|
||||
if (is_low_stock(id, nb))
|
||||
article_basket_html.find('.lowstock')
|
||||
.show();
|
||||
updateBasketRel();
|
||||
}
|
||||
}
|
||||
|
||||
function is_low_stock(id, nb) {
|
||||
var i = 0 ;
|
||||
while (i<articlesList.length && id != articlesList[i][1]) i++;
|
||||
article_data = articlesList[i];
|
||||
|
||||
stock = article_data[4] ;
|
||||
return (-5 <= stock - nb && stock - nb <= 5);
|
||||
}
|
||||
|
||||
function addDeposit(amount, is_checkout=1) {
|
||||
|
@ -848,7 +883,7 @@ $(document).ready(function() {
|
|||
.attr('data-opeindex', index)
|
||||
.find('.number').text(amount+"€").end()
|
||||
.find('.name').text(text).end()
|
||||
.find('.amount').text(amountToUKF(amount, account_data['is_cof']));
|
||||
.find('.amount').text(amountToUKF(amount, account_data['is_cof'], false));
|
||||
basket_container.prepend(deposit_basket_html);
|
||||
updateBasketRel();
|
||||
}
|
||||
|
@ -861,7 +896,7 @@ $(document).ready(function() {
|
|||
.attr('data-opeindex', index)
|
||||
.find('.number').text(amount+"€").end()
|
||||
.find('.name').text('Retrait').end()
|
||||
.find('.amount').text(amountToUKF(amount, account_data['is_cof']));
|
||||
.find('.amount').text(amountToUKF(amount, account_data['is_cof'], false));
|
||||
basket_container.prepend(withdraw_basket_html);
|
||||
updateBasketRel();
|
||||
}
|
||||
|
@ -871,11 +906,25 @@ $(document).ready(function() {
|
|||
});
|
||||
|
||||
$(document).on('keydown', function (e) {
|
||||
if (e.keyCode == 46) {
|
||||
// DEL (Suppr)
|
||||
basket_container.find('.ui-selected').each(function () {
|
||||
deleteFromBasket($(this).data('opeindex'));
|
||||
});
|
||||
switch(e.which) {
|
||||
case 46:
|
||||
// DEL (Suppr)
|
||||
basket_container.find('.ui-selected').each(function () {
|
||||
deleteFromBasket($(this).data('opeindex'));
|
||||
});
|
||||
break;
|
||||
case 38:
|
||||
// Arrow up
|
||||
basket_container.find('.ui-selected').each(function () {
|
||||
addExistingPurchase($(this).data('opeindex'), 1);
|
||||
});
|
||||
break;
|
||||
case 40:
|
||||
// Arrow down
|
||||
basket_container.find('.ui-selected').each(function () {
|
||||
addExistingPurchase($(this).data('opeindex'), -1);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -903,7 +952,7 @@ $(document).ready(function() {
|
|||
var amount = $(this).find('#id_form-'+opeindex+'-amount');
|
||||
if (!deleted && type == "purchase")
|
||||
amount.val(amountEuroPurchase(article_id, article_nb));
|
||||
basket_container.find('[data-opeindex='+opeindex+'] .amount').text(amountToUKF(amount.val(), account_data['is_cof']));
|
||||
basket_container.find('[data-opeindex='+opeindex+'] .amount').text(amountToUKF(amount.val(), account_data['is_cof'], false));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -922,9 +971,9 @@ $(document).ready(function() {
|
|||
basketrel_html += '<div>Sur 20€: '+ (20-amount).toFixed(2) +' €</div>';
|
||||
} else if (account_data['trigramme'] != '' && !isBasketEmpty()) {
|
||||
var amount = getAmountBasket();
|
||||
var amountUKF = amountToUKF(amount, account_data['is_cof']);
|
||||
var amountUKF = amountToUKF(amount, account_data['is_cof'], false);
|
||||
var newBalance = account_data['balance'] + amount;
|
||||
var newBalanceUKF = amountToUKF(newBalance, account_data['is_cof']);
|
||||
var newBalanceUKF = amountToUKF(newBalance, account_data['is_cof'], true);
|
||||
basketrel_html += '<div>Total: '+amountUKF+'</div>';
|
||||
basketrel_html += '<div>Nouveau solde: '+newBalanceUKF+'</div>';
|
||||
if (newBalance < 0)
|
||||
|
@ -939,6 +988,46 @@ $(document).ready(function() {
|
|||
updateBasketRel();
|
||||
}
|
||||
|
||||
function addExistingPurchase(opeindex, nb) {
|
||||
var type = formset_container.find("#id_form-"+opeindex+"-type").val();
|
||||
var id = formset_container.find("#id_form-"+opeindex+"-article").val();
|
||||
var nb_before = formset_container.find("#id_form-"+opeindex+"-article_nb").val();
|
||||
var nb_after = parseInt(nb_before) + parseInt(nb);
|
||||
var amountEuro_after = amountEuroPurchase(id, nb_after);
|
||||
var amountUKF_after = amountToUKF(amountEuro_after, account_data['is_cof']);
|
||||
|
||||
if (type == 'purchase') {
|
||||
if (nb_after == 0) {
|
||||
deleteFromBasket(opeindex);
|
||||
} else if (nb_after > 0 && nb_after <= 25) {
|
||||
if (nb_before > 0) {
|
||||
var article_html = basket_container.find('[data-opeindex='+opeindex+']');
|
||||
article_html.find('.amount').text(amountUKF_after).end()
|
||||
.find('.number').text('('+nb_after+'/'+article_data[4]+')').end() ;
|
||||
|
||||
} else {
|
||||
article_html = $(item_basket_default_html);
|
||||
article_html
|
||||
.attr('data-opeindex', opeindex)
|
||||
.find('.number').text('('+nb_after+'/'+article_data[4]+')').end()
|
||||
.find('.name').text(article_data[0]).end()
|
||||
.find('.amount').text(amountUKF_after);
|
||||
basket_container.prepend(article_basket_html);
|
||||
|
||||
}
|
||||
|
||||
if (is_low_stock(id, nb_after))
|
||||
article_html.find('.lowstock')
|
||||
.show();
|
||||
else
|
||||
article_html.find('.lowstock')
|
||||
.hide();
|
||||
updateExistingFormset(opeindex, nb_after, amountEuro_after);
|
||||
updateBasketRel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetBasket() {
|
||||
basket_container.find('tr').remove();
|
||||
mngmt_total_forms = 1;
|
||||
|
@ -948,6 +1037,7 @@ $(document).ready(function() {
|
|||
articleId.val(0);
|
||||
articleSelect.val('');
|
||||
articleNb.val('');
|
||||
articleStock.text('');
|
||||
displayMatchedArticles(articlesList);
|
||||
}
|
||||
|
||||
|
@ -1072,7 +1162,14 @@ $(document).ready(function() {
|
|||
}
|
||||
|
||||
function deleteFromFormset(opeindex) {
|
||||
formset_container.find('#id_form-'+opeindex+'-DELETE').prop('checked', true);
|
||||
updateExistingFormset(opeindex, 0, '0.00');
|
||||
}
|
||||
|
||||
function updateExistingFormset(opeindex, nb, amount) {
|
||||
formset_container
|
||||
.find('#id_form-'+opeindex+'-amount').val((parseFloat(amount)).toFixed(2)).end()
|
||||
.find('#id_form-'+opeindex+'-article_nb').val(nb).end()
|
||||
.find('#id_form-'+opeindex+'-DELETE').prop('checked', !nb);
|
||||
}
|
||||
|
||||
// -----
|
||||
|
@ -1251,6 +1348,8 @@ $(document).ready(function() {
|
|||
}
|
||||
for (var i=0; i<data['articles'].length; i++) {
|
||||
article = data['articles'][i];
|
||||
articles_container.find('#data-article-'+article['id'])
|
||||
.addClass('low-stock');
|
||||
articles_container.find('#data-article-'+article['id']+' .stock')
|
||||
.text(article['stock']);
|
||||
}
|
||||
|
|
5
kfet/templatetags/dictionary_extras.py
Normal file
5
kfet/templatetags/dictionary_extras.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.template.defaulttags import register
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
5
kfet/tests.py
Normal file
5
kfet/tests.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Écrire les tests ici
|
39
kfet/urls.py
39
kfet/urls.py
|
@ -68,6 +68,30 @@ urlpatterns = [
|
|||
(views.AccountNegativeList.as_view()),
|
||||
name='kfet.account.negative'),
|
||||
|
||||
# Account - Statistics
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/last/$',
|
||||
views.AccountStatLastAll.as_view(),
|
||||
name = 'kfet.account.stat.last'),
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/last/month/$',
|
||||
views.AccountStatLastMonth.as_view(),
|
||||
name = 'kfet.account.stat.last.month'),
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/last/week/$',
|
||||
views.AccountStatLastWeek.as_view(),
|
||||
name = 'kfet.account.stat.last.week'),
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/last/day/$',
|
||||
views.AccountStatLastDay.as_view(),
|
||||
name = 'kfet.account.stat.last.day'),
|
||||
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/balance/$',
|
||||
views.AccountStatBalanceAll.as_view(),
|
||||
name = 'kfet.account.stat.balance'),
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/balance/d/(?P<nb_date>\d*)/$',
|
||||
views.AccountStatBalance.as_view(),
|
||||
name = 'kfet.account.stat.balance.days'),
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/balance/anytime/$',
|
||||
views.AccountStatBalance.as_view(),
|
||||
name = 'kfet.account.stat.balance.anytime'),
|
||||
|
||||
# -----
|
||||
# Checkout urls
|
||||
# -----
|
||||
|
@ -125,7 +149,20 @@ urlpatterns = [
|
|||
# Article - Update
|
||||
url('^articles/(?P<pk>\d+)/edit$',
|
||||
teamkfet_required(views.ArticleUpdate.as_view()),
|
||||
name='kfet.article.update'),
|
||||
name = 'kfet.article.update'),
|
||||
# Article - Statistics
|
||||
url('^articles/(?P<pk>\d+)/stat/last/$',
|
||||
views.ArticleStatLastAll.as_view(),
|
||||
name = 'kfet.article.stat.last'),
|
||||
url('^articles/(?P<pk>\d+)/stat/last/month/$',
|
||||
views.ArticleStatLastMonth.as_view(),
|
||||
name = 'kfet.article.stat.last.month'),
|
||||
url('^articles/(?P<pk>\d+)/stat/last/week/$',
|
||||
views.ArticleStatLastWeek.as_view(),
|
||||
name = 'kfet.article.stat.last.week'),
|
||||
url('^articles/(?P<pk>\d+)/stat/last/day/$',
|
||||
views.ArticleStatLastDay.as_view(),
|
||||
name = 'kfet.article.stat.last.day'),
|
||||
|
||||
# -----
|
||||
# K-Psul urls
|
||||
|
|
726
kfet/views.py
726
kfet/views.py
|
@ -1,44 +1,61 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.cache import cache
|
||||
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.core.urlresolvers import reverse_lazy
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.contrib import messages
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib.auth.models import User, Permission, Group
|
||||
from django.http import HttpResponse, JsonResponse, Http404
|
||||
from django.forms import modelformset_factory, formset_factory
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import F, Sum, Prefetch, Count, Func
|
||||
from django.http import JsonResponse, Http404
|
||||
from django.forms import formset_factory
|
||||
from django.db import transaction
|
||||
from django.db.models import F, Sum, Prefetch, Count
|
||||
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,
|
||||
from kfet.models import (
|
||||
Account, Checkout, Article, Settings, AccountNegative,
|
||||
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
|
||||
InventoryArticle, Order, OrderArticle)
|
||||
from kfet.forms import *
|
||||
InventoryArticle, Order, OrderArticle, Operation, OperationGroup,
|
||||
TransferGroup, Transfer)
|
||||
from kfet.forms import (
|
||||
AccountTriForm, AccountBalanceForm, AccountNoTriForm, UserForm, CofForm,
|
||||
UserRestrictTeamForm, UserGroupForm, AccountForm, CofRestrictForm,
|
||||
AccountPwdForm, AccountNegativeForm, UserRestrictForm, AccountRestrictForm,
|
||||
GroupForm, CheckoutForm, CheckoutRestrictForm, CheckoutStatementCreateForm,
|
||||
CheckoutStatementUpdateForm, ArticleForm, ArticleRestrictForm,
|
||||
KPsulOperationGroupForm, KPsulAccountForm, KPsulCheckoutForm,
|
||||
KPsulOperationFormSet, AddcostForm, FilterHistoryForm, SettingsForm,
|
||||
TransferFormSet, InventoryArticleForm, OrderArticleForm,
|
||||
OrderArticleToInventoryForm
|
||||
)
|
||||
from collections import defaultdict
|
||||
from kfet import consumers
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
import django_cas_ng
|
||||
import hashlib
|
||||
import heapq
|
||||
import statistics
|
||||
from .statistic import daynames, monthnames, weeknames, \
|
||||
lastdays, lastweeks, lastmonths, \
|
||||
this_morning, this_monday_morning, this_first_month_day, \
|
||||
tot_ventes
|
||||
|
||||
|
||||
@login_required
|
||||
def home(request):
|
||||
return render(request, "kfet/base.html")
|
||||
return render(request, "kfet/home.html")
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def login_genericteam(request):
|
||||
|
@ -345,6 +362,7 @@ def account_read(request, trigramme):
|
|||
|
||||
# Account - Update
|
||||
|
||||
|
||||
@login_required
|
||||
def account_update(request, trigramme):
|
||||
account = get_object_or_404(Account, trigramme=trigramme)
|
||||
|
@ -355,39 +373,43 @@ def account_update(request, trigramme):
|
|||
raise PermissionDenied
|
||||
|
||||
if request.user.has_perm('kfet.is_team'):
|
||||
user_form = UserRestrictTeamForm(instance=account.user)
|
||||
group_form = UserGroupForm(instance=account.user)
|
||||
user_form = UserRestrictTeamForm(instance=account.user)
|
||||
group_form = UserGroupForm(instance=account.user)
|
||||
account_form = AccountForm(instance=account)
|
||||
cof_form = CofRestrictForm(instance=account.cofprofile)
|
||||
pwd_form = AccountPwdForm()
|
||||
cof_form = CofRestrictForm(instance=account.cofprofile)
|
||||
pwd_form = AccountPwdForm()
|
||||
if account.balance < 0 and not hasattr(account, 'negative'):
|
||||
AccountNegative.objects.create(account=account, start=timezone.now())
|
||||
AccountNegative.objects.create(account=account,
|
||||
start=timezone.now())
|
||||
account.refresh_from_db()
|
||||
if hasattr(account, 'negative'):
|
||||
negative_form = AccountNegativeForm(instance=account.negative)
|
||||
else:
|
||||
negative_form = None
|
||||
else:
|
||||
user_form = UserRestrictForm(instance=account.user)
|
||||
user_form = UserRestrictForm(instance=account.user)
|
||||
account_form = AccountRestrictForm(instance=account)
|
||||
cof_form = None
|
||||
group_form = None
|
||||
cof_form = None
|
||||
group_form = None
|
||||
negative_form = None
|
||||
pwd_form = None
|
||||
pwd_form = None
|
||||
|
||||
if request.method == "POST":
|
||||
# Update attempt
|
||||
success = False
|
||||
success = False
|
||||
missing_perm = True
|
||||
|
||||
if request.user.has_perm('kfet.is_team'):
|
||||
account_form = AccountForm(request.POST, instance=account)
|
||||
cof_form = CofRestrictForm(request.POST, instance=account.cofprofile)
|
||||
user_form = UserRestrictTeamForm(request.POST, instance=account.user)
|
||||
group_form = UserGroupForm(request.POST, instance=account.user)
|
||||
pwd_form = AccountPwdForm(request.POST)
|
||||
cof_form = CofRestrictForm(request.POST,
|
||||
instance=account.cofprofile)
|
||||
user_form = UserRestrictTeamForm(request.POST,
|
||||
instance=account.user)
|
||||
group_form = UserGroupForm(request.POST, instance=account.user)
|
||||
pwd_form = AccountPwdForm(request.POST)
|
||||
if hasattr(account, 'negative'):
|
||||
negative_form = AccountNegativeForm(request.POST, instance=account.negative)
|
||||
negative_form = AccountNegativeForm(request.POST,
|
||||
instance=account.negative)
|
||||
|
||||
if (request.user.has_perm('kfet.change_account')
|
||||
and account_form.is_valid() and cof_form.is_valid()
|
||||
|
@ -399,15 +421,14 @@ def account_update(request, trigramme):
|
|||
put_cleaned_data_in_dict(data, cof_form)
|
||||
|
||||
# Updating
|
||||
account_form.save(data = data)
|
||||
account_form.save(data=data)
|
||||
|
||||
# Checking perm to update password
|
||||
if (request.user.has_perm('kfet.change_account_password')
|
||||
and pwd_form.is_valid()):
|
||||
pwd = pwd_form.cleaned_data['pwd1']
|
||||
pwd_sha256 = hashlib.sha256(pwd.encode('utf-8')).hexdigest()
|
||||
Account.objects.filter(pk=account.pk).update(
|
||||
password = pwd_sha256)
|
||||
account.change_pwd(pwd)
|
||||
account.save()
|
||||
messages.success(request, 'Mot de passe mis à jour')
|
||||
|
||||
# Checking perm to manage perms
|
||||
|
@ -421,49 +442,66 @@ def account_update(request, trigramme):
|
|||
if account.negative.balance_offset:
|
||||
balance_offset_old = account.negative.balance_offset
|
||||
if (hasattr(account, 'negative')
|
||||
and request.user.has_perm('kfet.change_accountnegative')
|
||||
and request.user.has_perm('kfet.change_accountnegative')
|
||||
and negative_form.is_valid()):
|
||||
balance_offset_new = negative_form.cleaned_data['balance_offset']
|
||||
balance_offset_new = \
|
||||
negative_form.cleaned_data['balance_offset']
|
||||
if not balance_offset_new:
|
||||
balance_offset_new = 0
|
||||
balance_offset_diff = balance_offset_new - balance_offset_old
|
||||
balance_offset_diff = (balance_offset_new
|
||||
- balance_offset_old)
|
||||
Account.objects.filter(pk=account.pk).update(
|
||||
balance = F('balance') + balance_offset_diff)
|
||||
balance=F('balance') + balance_offset_diff)
|
||||
negative_form.save()
|
||||
if not balance_offset_new and Account.objects.get(pk=account.pk).balance >= 0:
|
||||
if Account.objects.get(pk=account.pk).balance >= 0 \
|
||||
and not balance_offset_new:
|
||||
AccountNegative.objects.get(account=account).delete()
|
||||
|
||||
success = True
|
||||
messages.success(request,
|
||||
'Informations du compte %s mises à jour' % account.trigramme)
|
||||
messages.success(
|
||||
request,
|
||||
'Informations du compte %s mises à jour'
|
||||
% account.trigramme)
|
||||
|
||||
# Modification de ses propres informations
|
||||
if request.user == account.user:
|
||||
missing_perm = False
|
||||
account.refresh_from_db()
|
||||
user_form = UserRestrictForm(request.POST, instance=account.user)
|
||||
account_form = AccountRestrictForm(request.POST, instance=account)
|
||||
pwd_form = AccountPwdForm(request.POST)
|
||||
|
||||
if user_form.is_valid() and account_form.is_valid():
|
||||
user_form.save()
|
||||
account_form.save()
|
||||
success = True
|
||||
messages.success(request, 'Vos informations ont été mises à jour')
|
||||
messages.success(request,
|
||||
'Vos informations ont été mises à jour')
|
||||
|
||||
if request.user.has_perm('kfet.is_team') \
|
||||
and pwd_form.is_valid():
|
||||
pwd = pwd_form.cleaned_data['pwd1']
|
||||
account.change_pwd(pwd)
|
||||
account.save()
|
||||
messages.success(
|
||||
request, 'Votre mot de passe a été mis à jour')
|
||||
|
||||
if missing_perm:
|
||||
messages.error(request, 'Permission refusée')
|
||||
if success:
|
||||
return redirect('kfet.account.read', account.trigramme)
|
||||
else:
|
||||
messages.error(request, 'Informations non mises à jour. Corrigez les erreurs')
|
||||
messages.error(
|
||||
request, 'Informations non mises à jour. Corrigez les erreurs')
|
||||
|
||||
return render(request, "kfet/account_update.html", {
|
||||
'account' : account,
|
||||
'account_form' : account_form,
|
||||
'cof_form' : cof_form,
|
||||
'user_form' : user_form,
|
||||
'group_form' : group_form,
|
||||
'account': account,
|
||||
'account_form': account_form,
|
||||
'cof_form': cof_form,
|
||||
'user_form': user_form,
|
||||
'group_form': group_form,
|
||||
'negative_form': negative_form,
|
||||
'pwd_form' : pwd_form,
|
||||
'pwd_form': pwd_form,
|
||||
})
|
||||
|
||||
@permission_required('kfet.manage_perms')
|
||||
|
@ -795,6 +833,8 @@ class ArticleUpdate(SuccessMessageMixin, UpdateView):
|
|||
# Updating
|
||||
return super(ArticleUpdate, self).form_valid(form)
|
||||
|
||||
|
||||
|
||||
# -----
|
||||
# K-Psul
|
||||
# -----
|
||||
|
@ -1944,3 +1984,591 @@ 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
|
||||
|
||||
|
||||
class JSONDetailView(JSONResponseMixin,
|
||||
BaseDetailView):
|
||||
"""
|
||||
Returns a DetailView that renders a JSON
|
||||
"""
|
||||
def render_to_response(self, context):
|
||||
return self.render_to_json_response(context)
|
||||
|
||||
class HybridDetailView(JSONResponseMixin,
|
||||
SingleObjectTemplateResponseMixin,
|
||||
BaseDetailView):
|
||||
"""
|
||||
Returns a DetailView as an html page except if a JSON file is requested
|
||||
by the GET method in which 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 an html page except if a JSON file is requested
|
||||
by the GET method in which 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(JSONDetailView):
|
||||
"""
|
||||
Summarize all the stats of an object
|
||||
Handles JSONResponse
|
||||
"""
|
||||
context_object_name = ''
|
||||
id_prefix = ''
|
||||
# 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 url_kwargs(self, **kwargs):
|
||||
return [{}] * self.nb_stat
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# On n'hérite pas
|
||||
object_id = self.object.id
|
||||
url_kwargs = self.url_kwargs()
|
||||
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(self.stat_urls[i],
|
||||
kwargs=dict(
|
||||
self.get_object_url_kwargs(),
|
||||
**url_kwargs[i]
|
||||
),
|
||||
),
|
||||
}
|
||||
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
|
||||
# -----------------------
|
||||
ID_PREFIX_ACC_BALANCE = "balance_acc"
|
||||
|
||||
|
||||
# Un résumé de toutes les vues ArticleStatBalance
|
||||
# REND DU JSON
|
||||
class AccountStatBalanceAll(ObjectResumeStat):
|
||||
model = Account
|
||||
context_object_name = 'account'
|
||||
trigramme_url_kwarg = 'trigramme'
|
||||
id_prefix = ID_PREFIX_ACC_BALANCE
|
||||
nb_stat = 5
|
||||
nb_default = 0
|
||||
stat_labels = ["Tout le temps", "1 an", "6 mois", "3 mois", "30 jours"]
|
||||
stat_urls = ['kfet.account.stat.balance.anytime'] \
|
||||
+ ['kfet.account.stat.balance.days'] * 4
|
||||
|
||||
def get_object(self, **kwargs):
|
||||
trigramme = self.kwargs.get(self.trigramme_url_kwarg)
|
||||
return get_object_or_404(Account, trigramme=trigramme)
|
||||
|
||||
def get_object_url_kwargs(self, **kwargs):
|
||||
return {'trigramme': self.object.trigramme}
|
||||
|
||||
def url_kwargs(self, **kwargs):
|
||||
context_list = (super(AccountStatBalanceAll, self)
|
||||
.url_kwargs(**kwargs))
|
||||
context_list[1] = {'nb_date': 365}
|
||||
context_list[2] = {'nb_date': 183}
|
||||
context_list[3] = {'nb_date': 90}
|
||||
context_list[4] = {'nb_date': 30}
|
||||
return context_list
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(AccountStatBalanceAll, self).dispatch(*args, **kwargs)
|
||||
|
||||
|
||||
class AccountStatBalance(JSONDetailView):
|
||||
"""
|
||||
Returns a JSON containing the evolution a the personnal
|
||||
balance of a trigramme between timezone.now() and `nb_days`
|
||||
ago (specified to the view as an argument)
|
||||
takes into account the Operations and the Transfers
|
||||
does not takes into account the balance offset
|
||||
"""
|
||||
model = Account
|
||||
trigramme_url_kwarg = 'trigramme'
|
||||
nb_date_url_kwargs = 'nb_date'
|
||||
context_object_name = 'account'
|
||||
id_prefix = ""
|
||||
|
||||
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
|
||||
nb_date = self.kwargs.get(self.nb_date_url_kwargs, None)
|
||||
end_date = this_morning()
|
||||
if nb_date is None:
|
||||
begin_date = timezone.datetime(year=1980, month=1, day=1)
|
||||
anytime = True
|
||||
else:
|
||||
begin_date = this_morning() \
|
||||
- timezone.timedelta(days=int(nb_date))
|
||||
anytime = False
|
||||
# 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=begin_date)
|
||||
.filter(at__lte=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=begin_date)
|
||||
.filter(group__at__lte=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=begin_date)
|
||||
.filter(group__at__lte=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': 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 anytime:
|
||||
actions += [
|
||||
# Date de début :
|
||||
{
|
||||
'at': 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()
|
||||
nb_days = self.kwargs.get(self.nb_date_url_kwargs, None)
|
||||
if nb_days is None:
|
||||
nb_days_string = 'anytime'
|
||||
else:
|
||||
nb_days_string = str(int(nb_days))
|
||||
context['charts'] = [ { "color": "rgb(255, 99, 132)",
|
||||
"label": "Balance",
|
||||
"values": changes } ]
|
||||
context['is_time_chart'] = True
|
||||
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)
|
||||
|
||||
|
||||
# ------------------------
|
||||
# Consommation personnelle
|
||||
# ------------------------
|
||||
ID_PREFIX_ACC_LAST = "last_acc"
|
||||
ID_PREFIX_ACC_LAST_DAYS = "last_days_acc"
|
||||
ID_PREFIX_ACC_LAST_WEEKS = "last_weeks_acc"
|
||||
ID_PREFIX_ACC_LAST_MONTHS = "last_months_acc"
|
||||
|
||||
|
||||
# Un résumé de toutes les vues ArticleStatLast
|
||||
# NE REND PAS DE JSON
|
||||
class AccountStatLastAll(ObjectResumeStat):
|
||||
model = Account
|
||||
context_object_name = 'account'
|
||||
trigramme_url_kwarg = 'trigramme'
|
||||
id_prefix = ID_PREFIX_ACC_LAST
|
||||
nb_stat = 3
|
||||
nb_default = 2
|
||||
stat_labels = ["Derniers mois", "Dernières semaines", "Derniers jours"]
|
||||
stat_urls = ['kfet.account.stat.last.month',
|
||||
'kfet.account.stat.last.week',
|
||||
'kfet.account.stat.last.day']
|
||||
|
||||
def get_object(self, **kwargs):
|
||||
trigramme = self.kwargs.get(self.trigramme_url_kwarg)
|
||||
return get_object_or_404(Account, trigramme=trigramme)
|
||||
|
||||
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(JSONDetailView):
|
||||
"""
|
||||
Returns a JSON containing the evolution a the personnal
|
||||
consommation of a trigramme at the diffent dates specified
|
||||
"""
|
||||
model = Account
|
||||
trigramme_url_kwarg = 'trigramme'
|
||||
context_object_name = 'account'
|
||||
end_date = timezone.now()
|
||||
id_prefix = ""
|
||||
|
||||
# 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):
|
||||
return {}
|
||||
|
||||
# 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):
|
||||
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['charts'] = [ { "color": "rgb(255, 99, 132)",
|
||||
"label": "NB items achetés",
|
||||
"values": nb_ventes } ]
|
||||
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
|
||||
class AccountStatLastDay(AccountStatLast):
|
||||
end_date = this_morning()
|
||||
id_prefix = ID_PREFIX_ACC_LAST_DAYS
|
||||
|
||||
def get_dates(self, **kwargs):
|
||||
return lastdays(7)
|
||||
|
||||
def get_labels(self, **kwargs):
|
||||
days = lastdays(7)
|
||||
return daynames(days)
|
||||
|
||||
|
||||
# Rend les achats de ce compte des 7 dernières semaines
|
||||
# La semaine en cours n'est pas comprise
|
||||
class AccountStatLastWeek(AccountStatLast):
|
||||
end_date = this_monday_morning()
|
||||
id_prefix = ID_PREFIX_ACC_LAST_WEEKS
|
||||
|
||||
def get_dates(self, **kwargs):
|
||||
return lastweeks(7)
|
||||
|
||||
def get_labels(self, **kwargs):
|
||||
weeks = lastweeks(7)
|
||||
return weeknames(weeks)
|
||||
|
||||
|
||||
# Rend les achats de ce compte des 7 derniers mois
|
||||
# Le mois en cours n'est pas compris
|
||||
class AccountStatLastMonth(AccountStatLast):
|
||||
end_date = this_monday_morning()
|
||||
id_prefix = ID_PREFIX_ACC_LAST_MONTHS
|
||||
|
||||
def get_dates(self, **kwargs):
|
||||
return lastmonths(7)
|
||||
|
||||
def get_labels(self, **kwargs):
|
||||
months = lastmonths(7)
|
||||
return monthnames(months)
|
||||
|
||||
|
||||
# ------------------------
|
||||
# Article Satistiques Last
|
||||
# ------------------------
|
||||
ID_PREFIX_ART_LAST = "last_art"
|
||||
ID_PREFIX_ART_LAST_DAYS = "last_days_art"
|
||||
ID_PREFIX_ART_LAST_WEEKS = "last_weeks_art"
|
||||
ID_PREFIX_ART_LAST_MONTHS = "last_months_art"
|
||||
|
||||
|
||||
# Un résumé de toutes les vues ArticleStatLast
|
||||
# NE REND PAS DE JSON
|
||||
class ArticleStatLastAll(ObjectResumeStat):
|
||||
model = Article
|
||||
context_object_name = 'article'
|
||||
id_prefix = ID_PREFIX_ART_LAST
|
||||
nb_stat = 3
|
||||
nb_default = 2
|
||||
stat_labels = ["Derniers mois", "Dernières semaines", "Derniers jours"]
|
||||
stat_urls = ['kfet.article.stat.last.month',
|
||||
'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)
|
||||
|
||||
|
||||
class ArticleStatLast(JSONDetailView):
|
||||
"""
|
||||
Returns a JSON containing the consommation
|
||||
of an article at the diffent dates precised
|
||||
"""
|
||||
model = Article
|
||||
context_object_name = 'article'
|
||||
end_date = timezone.now()
|
||||
id_prefix = ""
|
||||
|
||||
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):
|
||||
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['charts'] = [ { "color": "rgb(255, 99, 132)",
|
||||
"label": "Toutes consommations",
|
||||
"values": nb_ventes },
|
||||
{ "color": "rgb(54, 162, 235)",
|
||||
"label": "LIQ",
|
||||
"values": nb_liq },
|
||||
{ "color": "rgb(255, 205, 86)",
|
||||
"label": "Comptes K-Fêt",
|
||||
"values": nb_accounts } ]
|
||||
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
|
||||
class ArticleStatLastDay(ArticleStatLast):
|
||||
end_date = this_morning()
|
||||
id_prefix = ID_PREFIX_ART_LAST_DAYS
|
||||
|
||||
def get_dates(self, **kwargs):
|
||||
return lastdays(7)
|
||||
|
||||
def get_labels(self, **kwargs):
|
||||
days = lastdays(7)
|
||||
return daynames(days)
|
||||
|
||||
|
||||
# Rend les ventes de 7 dernières semaines
|
||||
# La semaine en cours n'est pas comprise
|
||||
class ArticleStatLastWeek(ArticleStatLast):
|
||||
end_date = this_monday_morning()
|
||||
id_prefix = ID_PREFIX_ART_LAST_WEEKS
|
||||
|
||||
def get_dates(self, **kwargs):
|
||||
return lastweeks(7)
|
||||
|
||||
def get_labels(self, **kwargs):
|
||||
weeks = lastweeks(7)
|
||||
return weeknames(weeks)
|
||||
|
||||
|
||||
# Rend les ventes des 7 derniers mois
|
||||
# Le mois en cours n'est pas compris
|
||||
class ArticleStatLastMonth(ArticleStatLast):
|
||||
end_date = this_monday_morning()
|
||||
id_prefix = ID_PREFIX_ART_LAST_MONTHS
|
||||
|
||||
def get_dates(self, **kwargs):
|
||||
return lastmonths(7)
|
||||
|
||||
def get_labels(self, **kwargs):
|
||||
months = lastmonths(7)
|
||||
return monthnames(months)
|
||||
|
|
Loading…
Reference in a new issue