diff --git a/kfet/forms.py b/kfet/forms.py
index 80504b03..317effb3 100644
--- a/kfet/forms.py
+++ b/kfet/forms.py
@@ -1,21 +1,17 @@
# -*- coding: utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-from builtins import *
-
from decimal import Decimal
from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
-from django.forms import modelformset_factory, inlineformset_factory
-from django.forms.models import BaseInlineFormSet
+from django.forms import modelformset_factory
from django.utils import timezone
-from kfet.models import (Account, Checkout, Article, OperationGroup, Operation,
+from kfet.models import (
+ Account, Checkout, Article, OperationGroup, Operation,
CheckoutStatement, ArticleCategory, Settings, AccountNegative, Transfer,
- TransferGroup, Supplier, Inventory, InventoryArticle)
+ TransferGroup, Supplier)
from gestioncof.models import CofProfile
# -----
@@ -131,7 +127,16 @@ class UserRestrictTeamForm(UserForm):
class UserGroupForm(forms.ModelForm):
groups = forms.ModelMultipleChoiceField(
- Group.objects.filter(name__icontains='K-Fêt'))
+ Group.objects.filter(name__icontains='K-Fêt'),
+ required=False)
+
+ def clean_groups(self):
+ groups = self.cleaned_data.get('groups')
+ # Si aucun groupe, on le dénomme
+ if not groups:
+ groups = self.instance.groups.exclude(name__icontains='K-Fêt')
+ return groups
+
class Meta:
model = User
fields = ['groups']
diff --git a/kfet/models.py b/kfet/models.py
index 55ae7b76..ca9fa177 100644
--- a/kfet/models.py
+++ b/kfet/models.py
@@ -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
@@ -561,7 +568,7 @@ class Operation(models.Model):
templates = {
self.PURCHASE: "{nb} {article.name} ({amount}€)",
self.DEPOSIT: "charge ({amount})",
- self.WITHDRAW: "retrait ({amount})",
+ self.WITHDRAW: "retrait ({amount})",
self.INITIAL: "initial ({amount})",
}
return templates[self.type].format(nb=self.article_nb,
diff --git a/kfet/static/kfet/css/kpsul.css b/kfet/static/kfet/css/kpsul.css
index e7950914..ba88e433 100644
--- a/kfet/static/kfet/css/kpsul.css
+++ b/kfet/static/kfet/css/kpsul.css
@@ -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;
diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js
index 31758c36..bab21c12 100644
--- a/kfet/static/kfet/js/kfet.js
+++ b/kfet/static/kfet/js/kfet.js
@@ -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) {
diff --git a/kfet/static/kfet/js/statistic.js b/kfet/static/kfet/js/statistic.js
index 258f8cc8..7ab56f1d 100644
--- a/kfet/static/kfet/js/statistic.js
+++ b/kfet/static/kfet/js/statistic.js
@@ -1,18 +1,197 @@
-var STAT = {};
+(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 = $("
");
+ var buttons;
-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
- STAT.get_thing = function(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);
- }
- });
- }
-});
+ 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 = $("