WIP: Aureplop/kpsul js refactor #501
27 changed files with 655 additions and 393 deletions
|
@ -18,7 +18,10 @@ class Tirage(models.Model):
|
||||||
fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
|
fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
|
||||||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
||||||
active = models.BooleanField("Tirage actif", default=False)
|
active = models.BooleanField("Tirage actif", default=False)
|
||||||
appear_catalogue = models.BooleanField("Tirage à afficher dans le catalogue", default=False)
|
appear_catalogue = models.BooleanField(
|
||||||
|
"Tirage à afficher dans le catalogue",
|
||||||
|
default=False
|
||||||
|
)
|
||||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
|
|
89
bda/tests.py
89
bda/tests.py
|
@ -1,22 +1,79 @@
|
||||||
# -*- coding: utf-8 -*-
|
import json
|
||||||
"""
|
|
||||||
This file demonstrates writing tests using the unittest module. These will pass
|
|
||||||
when you run "manage.py test".
|
|
||||||
|
|
||||||
Replace this with more appropriate tests for your application.
|
from django.test import TestCase, Client
|
||||||
"""
|
from django.utils import timezone
|
||||||
|
|
||||||
from __future__ import division
|
from .models import Tirage, Spectacle, Salle, CategorieSpectacle
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
|
|
||||||
from django.test import TestCase
|
class TestBdAViews(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tirage = Tirage.objects.create(
|
||||||
|
title="Test tirage",
|
||||||
|
appear_catalogue=True,
|
||||||
|
ouverture=timezone.now(),
|
||||||
|
fermeture=timezone.now(),
|
||||||
|
)
|
||||||
|
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||||
|
self.location = Salle.objects.create(name="here")
|
||||||
|
Spectacle.objects.bulk_create([
|
||||||
|
Spectacle(
|
||||||
|
title="foo", date=timezone.now(), location=self.location,
|
||||||
|
price=0, slots=42, tirage=self.tirage, listing=False,
|
||||||
|
category=self.category
|
||||||
|
),
|
||||||
|
Spectacle(
|
||||||
|
title="bar", date=timezone.now(), location=self.location,
|
||||||
|
price=1, slots=142, tirage=self.tirage, listing=False,
|
||||||
|
category=self.category
|
||||||
|
),
|
||||||
|
Spectacle(
|
||||||
|
title="baz", date=timezone.now(), location=self.location,
|
||||||
|
price=2, slots=242, tirage=self.tirage, listing=False,
|
||||||
|
category=self.category
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_catalogue(self):
|
||||||
|
"""Test the catalogue JSON API"""
|
||||||
|
client = Client()
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
# The `list` hooh
|
||||||
def test_basic_addition(self):
|
resp = client.get("/bda/catalogue/list")
|
||||||
"""
|
self.assertJSONEqual(
|
||||||
Tests that 1 + 1 always equals 2.
|
resp.content.decode("utf-8"),
|
||||||
"""
|
[{"id": self.tirage.id, "title": self.tirage.title}]
|
||||||
self.assertEqual(1 + 1, 2)
|
)
|
||||||
|
|
||||||
|
# The `details` hook
|
||||||
|
resp = client.get(
|
||||||
|
"/bda/catalogue/details?id={}".format(self.tirage.id)
|
||||||
|
)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
resp.content.decode("utf-8"),
|
||||||
|
{
|
||||||
|
"categories": [{
|
||||||
|
"id": self.category.id,
|
||||||
|
"name": self.category.name
|
||||||
|
}],
|
||||||
|
"locations": [{
|
||||||
|
"id": self.location.id,
|
||||||
|
"name": self.location.name
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# The `descriptions` hook
|
||||||
|
resp = client.get(
|
||||||
|
"/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
||||||
|
)
|
||||||
|
raw = resp.content.decode("utf-8")
|
||||||
|
try:
|
||||||
|
results = json.loads(raw)
|
||||||
|
except ValueError:
|
||||||
|
self.fail("Not valid JSON: {}".format(raw))
|
||||||
|
self.assertEqual(len(results), 3)
|
||||||
|
self.assertEqual(
|
||||||
|
{(s["title"], s["price"], s["slots"]) for s in results},
|
||||||
|
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)}
|
||||||
|
)
|
||||||
|
|
87
bda/views.py
87
bda/views.py
|
@ -22,7 +22,6 @@ from django.core.urlresolvers import reverse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone, formats
|
from django.utils import timezone, formats
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from gestioncof.decorators import cof_required, buro_required
|
from gestioncof.decorators import cof_required, buro_required
|
||||||
from bda.models import (
|
from bda.models import (
|
||||||
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
|
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
|
||||||
|
@ -657,29 +656,35 @@ def catalogue(request, request_type):
|
||||||
if request_type == "list":
|
if request_type == "list":
|
||||||
# Dans ce cas on retourne la liste des tirages et de leur id en JSON
|
# Dans ce cas on retourne la liste des tirages et de leur id en JSON
|
||||||
data_return = list(
|
data_return = list(
|
||||||
Tirage.objects.filter(appear_catalogue=True).values('id', 'title'))
|
Tirage.objects.filter(appear_catalogue=True).values('id', 'title')
|
||||||
|
)
|
||||||
return JsonResponse(data_return, safe=False)
|
return JsonResponse(data_return, safe=False)
|
||||||
if request_type == "details":
|
if request_type == "details":
|
||||||
# Dans ce cas on retourne une liste des catégories et des salles
|
# Dans ce cas on retourne une liste des catégories et des salles
|
||||||
tirage_id = request.GET.get('id', '')
|
tirage_id = request.GET.get('id', None)
|
||||||
try:
|
if tirage_id is None:
|
||||||
tirage = Tirage.objects.get(id=tirage_id)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest(
|
||||||
"Aucun tirage correspondant à l'id "
|
"Missing GET parameter: id <int>"
|
||||||
+ tirage_id)
|
)
|
||||||
|
try:
|
||||||
|
tirage = get_object_or_404(Tirage, id=int(tirage_id))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest(
|
||||||
"Mauvais format d'identifiant : "
|
"Bad format: int expected for `id`"
|
||||||
+ tirage_id)
|
)
|
||||||
|
shows = tirage.spectacle_set.values_list("id", flat=True)
|
||||||
categories = list(
|
categories = list(
|
||||||
CategorieSpectacle.objects.filter(
|
CategorieSpectacle.objects
|
||||||
spectacle__in=tirage.spectacle_set.all())
|
.filter(spectacle__in=shows)
|
||||||
.distinct().values('id', 'name'))
|
.distinct()
|
||||||
|
.values('id', 'name')
|
||||||
|
)
|
||||||
locations = list(
|
locations = list(
|
||||||
Salle.objects.filter(
|
Salle.objects
|
||||||
spectacle__in=tirage.spectacle_set.all())
|
.filter(spectacle__in=shows)
|
||||||
.distinct().values('id', 'name'))
|
.distinct()
|
||||||
|
.values('id', 'name')
|
||||||
|
)
|
||||||
data_return = {'categories': categories, 'locations': locations}
|
data_return = {'categories': categories, 'locations': locations}
|
||||||
return JsonResponse(data_return, safe=False)
|
return JsonResponse(data_return, safe=False)
|
||||||
if request_type == "descriptions":
|
if request_type == "descriptions":
|
||||||
|
@ -687,33 +692,35 @@ def catalogue(request, request_type):
|
||||||
# à la salle spécifiées
|
# à la salle spécifiées
|
||||||
|
|
||||||
tirage_id = request.GET.get('id', '')
|
tirage_id = request.GET.get('id', '')
|
||||||
categories = request.GET.get('category', '[0]')
|
categories = request.GET.get('category', '[]')
|
||||||
locations = request.GET.get('location', '[0]')
|
locations = request.GET.get('location', '[]')
|
||||||
try:
|
try:
|
||||||
category_id = json.loads(categories)
|
tirage_id = int(tirage_id)
|
||||||
location_id = json.loads(locations)
|
categories_id = json.loads(categories)
|
||||||
tirage = Tirage.objects.get(id=tirage_id)
|
locations_id = json.loads(locations)
|
||||||
|
# Integers expected
|
||||||
shows_qs = tirage.spectacle_set
|
if not all(isinstance(id, int) for id in categories_id):
|
||||||
if not(0 in category_id):
|
raise ValueError
|
||||||
shows_qs = shows_qs.filter(
|
if not all(isinstance(id, int) for id in locations_id):
|
||||||
category__id__in=category_id)
|
raise ValueError
|
||||||
if not(0 in location_id):
|
|
||||||
shows_qs = shows_qs.filter(
|
|
||||||
location__id__in=location_id)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return HttpResponseBadRequest(
|
|
||||||
"Impossible de trouver des résultats correspondant "
|
|
||||||
"à ces caractéristiques : "
|
|
||||||
+ "id = " + tirage_id
|
|
||||||
+ ", catégories = " + categories
|
|
||||||
+ ", salles = " + locations)
|
|
||||||
except ValueError: # Contient JSONDecodeError
|
except ValueError: # Contient JSONDecodeError
|
||||||
return HttpResponseBadRequest(
|
return HttpResponseBadRequest(
|
||||||
"Impossible de parser les paramètres donnés : "
|
"Parse error, please ensure the GET parameters have the "
|
||||||
+ "id = " + request.GET.get('id', '')
|
"following types:\n"
|
||||||
+ ", catégories = " + request.GET.get('category', '[0]')
|
"id: int, category: [int], location: [int]\n"
|
||||||
+ ", salles = " + request.GET.get('location', '[0]'))
|
"Data received:\n"
|
||||||
|
"id = {}, category = {}, locations = {}"
|
||||||
|
.format(request.GET.get('id', ''),
|
||||||
|
request.GET.get('category', '[]'),
|
||||||
|
request.GET.get('location', '[]'))
|
||||||
|
)
|
||||||
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
|
|
||||||
|
shows_qs = tirage.spectacle_set
|
||||||
|
if categories_id:
|
||||||
|
shows_qs = shows_qs.filter(category__id__in=categories_id)
|
||||||
|
if locations_id:
|
||||||
|
shows_qs = shows_qs.filter(location__id__in=locations_id)
|
||||||
|
|
||||||
# On convertit les descriptions à envoyer en une liste facilement
|
# On convertit les descriptions à envoyer en une liste facilement
|
||||||
# JSONifiable (il devrait y avoir un moyen plus efficace en
|
# JSONifiable (il devrait y avoir un moyen plus efficace en
|
||||||
|
|
|
@ -48,6 +48,7 @@ INSTALLED_APPS = (
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
'django_js_reverse',
|
'django_js_reverse',
|
||||||
'custommail',
|
'custommail',
|
||||||
|
'djconfig',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
|
@ -61,6 +62,7 @@ MIDDLEWARE_CLASSES = (
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'djconfig.middleware.DjConfigMiddleware',
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'cof.urls'
|
ROOT_URLCONF = 'cof.urls'
|
||||||
|
@ -79,8 +81,10 @@ TEMPLATES = [
|
||||||
'django.core.context_processors.i18n',
|
'django.core.context_processors.i18n',
|
||||||
'django.core.context_processors.media',
|
'django.core.context_processors.media',
|
||||||
'django.core.context_processors.static',
|
'django.core.context_processors.static',
|
||||||
|
'djconfig.context_processors.config',
|
||||||
'gestioncof.shared.context_processor',
|
'gestioncof.shared.context_processor',
|
||||||
'kfet.context_processors.auth',
|
'kfet.context_processors.auth',
|
||||||
|
'kfet.context_processors.config',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,3 +12,9 @@ class KFetConfig(AppConfig):
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import kfet.signals
|
import kfet.signals
|
||||||
|
self.register_config()
|
||||||
|
|
||||||
|
def register_config(self):
|
||||||
|
import djconfig
|
||||||
|
from kfet.forms import KFetConfigForm
|
||||||
|
djconfig.register(KFetConfigForm)
|
||||||
|
|
71
kfet/config.py
Normal file
71
kfet/config.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from djconfig import config
|
||||||
|
|
||||||
|
|
||||||
|
class KFetConfig(object):
|
||||||
|
"""kfet app configuration.
|
||||||
|
|
||||||
|
Enhance isolation with backend used to store config.
|
||||||
|
Usable after DjConfig middleware was called.
|
||||||
|
|
||||||
|
"""
|
||||||
|
prefix = 'kfet_'
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
if key == 'subvention_cof':
|
||||||
|
# Allows accessing to the reduction as a subvention
|
||||||
|
# Other reason: backward compatibility
|
||||||
|
reduction_mult = 1 - self.reduction_cof/100
|
||||||
|
return (1/reduction_mult - 1) * 100
|
||||||
|
return getattr(config, self._get_dj_key(key))
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
"""Get list of kfet app configuration.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(key, value) for each configuration entry as list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# prevent circular imports
|
||||||
|
from kfet.forms import KFetConfigForm
|
||||||
|
return [(field.label, getattr(config, name), )
|
||||||
|
for name, field in KFetConfigForm.base_fields.items()]
|
||||||
|
|
||||||
|
def _get_dj_key(self, key):
|
||||||
|
return '{}{}'.format(self.prefix, key)
|
||||||
|
|
||||||
|
def set(self, **kwargs):
|
||||||
|
"""Update configuration value(s).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: Keyword arguments. Keys must be in kfet config.
|
||||||
|
Config entries are updated to given values.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# prevent circular imports
|
||||||
|
from kfet.forms import KFetConfigForm
|
||||||
|
|
||||||
|
# get old config
|
||||||
|
new_cfg = KFetConfigForm().initial
|
||||||
|
# update to new config
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
dj_key = self._get_dj_key(key)
|
||||||
|
if isinstance(value, models.Model):
|
||||||
|
new_cfg[dj_key] = value.pk
|
||||||
|
else:
|
||||||
|
new_cfg[dj_key] = value
|
||||||
|
# save new config
|
||||||
|
cfg_form = KFetConfigForm(new_cfg)
|
||||||
|
if cfg_form.is_valid():
|
||||||
|
cfg_form.save()
|
||||||
|
else:
|
||||||
|
raise ValidationError(
|
||||||
|
'Invalid values in kfet_config.set: %(fields)s',
|
||||||
|
params={'fields': list(cfg_form.errors)})
|
||||||
|
|
||||||
|
|
||||||
|
kfet_config = KFetConfig()
|
|
@ -1,26 +1,41 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import (absolute_import, division,
|
from django.core.serializers.json import json, DjangoJSONEncoder
|
||||||
print_function, unicode_literals)
|
|
||||||
from builtins import *
|
|
||||||
|
|
||||||
from channels import Group
|
|
||||||
from channels.generic.websockets import JsonWebsocketConsumer
|
from channels.generic.websockets import JsonWebsocketConsumer
|
||||||
|
|
||||||
class KPsul(JsonWebsocketConsumer):
|
|
||||||
|
|
||||||
# Set to True if you want them, else leave out
|
class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
|
||||||
strict_ordering = False
|
"""Custom Json Websocket Consumer.
|
||||||
slight_ordering = False
|
|
||||||
|
|
||||||
def connection_groups(self, **kwargs):
|
Encode to JSON with DjangoJSONEncoder.
|
||||||
return ['kfet.kpsul']
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def encode_json(cls, content):
|
||||||
|
return json.dumps(content, cls=DjangoJSONEncoder)
|
||||||
|
|
||||||
|
|
||||||
|
class PermConsumerMixin(object):
|
||||||
|
"""Add support to check permissions on Consumers.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
perms_connect (list): Required permissions to connect to this
|
||||||
|
consumer.
|
||||||
|
|
||||||
|
"""
|
||||||
|
http_user = True # Enable message.user
|
||||||
|
perms_connect = []
|
||||||
|
|
||||||
def connect(self, message, **kwargs):
|
def connect(self, message, **kwargs):
|
||||||
pass
|
"""Check permissions on connection."""
|
||||||
|
if message.user.has_perms(self.perms_connect):
|
||||||
|
super().connect(message, **kwargs)
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
|
||||||
def receive(self, content, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def disconnect(self, message, **kwargs):
|
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
||||||
pass
|
groups = ['kfet.kpsul']
|
||||||
|
perms_connect = ['kfet.is_team']
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import (absolute_import, division,
|
|
||||||
print_function, unicode_literals)
|
|
||||||
from builtins import *
|
|
||||||
|
|
||||||
from django.contrib.auth.context_processors import PermWrapper
|
from django.contrib.auth.context_processors import PermWrapper
|
||||||
|
|
||||||
|
from kfet.config import kfet_config
|
||||||
|
|
||||||
|
|
||||||
def auth(request):
|
def auth(request):
|
||||||
if hasattr(request, 'real_user'):
|
if hasattr(request, 'real_user'):
|
||||||
return {
|
return {
|
||||||
|
@ -13,3 +12,7 @@ def auth(request):
|
||||||
'perms': PermWrapper(request.real_user),
|
'perms': PermWrapper(request.real_user),
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def config(request):
|
||||||
|
return {'kfet_config': kfet_config}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MinLengthValidator
|
from django.core.validators import MinLengthValidator
|
||||||
|
@ -8,12 +10,16 @@ from django.contrib.auth.models import User, Group, Permission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.forms import modelformset_factory
|
from django.forms import modelformset_factory
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from djconfig.forms import ConfigForm
|
||||||
|
|
||||||
from kfet.models import (
|
from kfet.models import (
|
||||||
Account, Checkout, Article, OperationGroup, Operation,
|
Account, Checkout, Article, OperationGroup, Operation,
|
||||||
CheckoutStatement, ArticleCategory, Settings, AccountNegative, Transfer,
|
CheckoutStatement, ArticleCategory, AccountNegative, Transfer,
|
||||||
TransferGroup, Supplier)
|
TransferGroup, Supplier)
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Widgets
|
# Widgets
|
||||||
# -----
|
# -----
|
||||||
|
@ -389,40 +395,46 @@ class AddcostForm(forms.Form):
|
||||||
self.cleaned_data['amount'] = 0
|
self.cleaned_data['amount'] = 0
|
||||||
super(AddcostForm, self).clean()
|
super(AddcostForm, self).clean()
|
||||||
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Settings forms
|
# Settings forms
|
||||||
# -----
|
# -----
|
||||||
|
|
||||||
class SettingsForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Settings
|
|
||||||
fields = ['value_decimal', 'value_account', 'value_duration']
|
|
||||||
|
|
||||||
def clean(self):
|
class KFetConfigForm(ConfigForm):
|
||||||
name = self.instance.name
|
|
||||||
value_decimal = self.cleaned_data.get('value_decimal')
|
|
||||||
value_account = self.cleaned_data.get('value_account')
|
|
||||||
value_duration = self.cleaned_data.get('value_duration')
|
|
||||||
|
|
||||||
type_decimal = ['SUBVENTION_COF', 'ADDCOST_AMOUNT', 'OVERDRAFT_AMOUNT']
|
kfet_reduction_cof = forms.DecimalField(
|
||||||
type_account = ['ADDCOST_FOR']
|
label='Réduction COF', initial=Decimal('20'),
|
||||||
type_duration = ['OVERDRAFT_DURATION', 'CANCEL_DURATION']
|
max_digits=6, decimal_places=2,
|
||||||
|
help_text="Réduction, à donner en pourcentage, appliquée lors d'un "
|
||||||
|
"achat par un-e membre du COF sur le montant en euros.",
|
||||||
|
)
|
||||||
|
kfet_addcost_amount = forms.DecimalField(
|
||||||
|
label='Montant de la majoration (en €)', initial=Decimal('0'),
|
||||||
|
required=False,
|
||||||
|
max_digits=6, decimal_places=2,
|
||||||
|
)
|
||||||
|
kfet_addcost_for = forms.ModelChoiceField(
|
||||||
|
label='Destinataire de la majoration', initial=None, required=False,
|
||||||
|
help_text='Laissez vide pour désactiver la majoration.',
|
||||||
|
queryset=(Account.objects
|
||||||
|
.select_related('cofprofile', 'cofprofile__user')
|
||||||
|
.all()),
|
||||||
|
)
|
||||||
|
kfet_overdraft_duration = forms.DurationField(
|
||||||
|
label='Durée du découvert autorisé par défaut',
|
||||||
|
initial=timedelta(days=1),
|
||||||
|
)
|
||||||
|
kfet_overdraft_amount = forms.DecimalField(
|
||||||
|
label='Montant du découvert autorisé par défaut (en €)',
|
||||||
|
initial=Decimal('20'),
|
||||||
|
max_digits=6, decimal_places=2,
|
||||||
|
)
|
||||||
|
kfet_cancel_duration = forms.DurationField(
|
||||||
|
label='Durée pour annuler une commande sans mot de passe',
|
||||||
|
initial=timedelta(minutes=5),
|
||||||
|
)
|
||||||
|
|
||||||
self.cleaned_data['name'] = name
|
|
||||||
if name in type_decimal:
|
|
||||||
if not value_decimal:
|
|
||||||
raise ValidationError('Renseignez une valeur décimale')
|
|
||||||
self.cleaned_data['value_account'] = None
|
|
||||||
self.cleaned_data['value_duration'] = None
|
|
||||||
elif name in type_account:
|
|
||||||
self.cleaned_data['value_decimal'] = None
|
|
||||||
self.cleaned_data['value_duration'] = None
|
|
||||||
elif name in type_duration:
|
|
||||||
if not value_duration:
|
|
||||||
raise ValidationError('Renseignez une durée')
|
|
||||||
self.cleaned_data['value_decimal'] = None
|
|
||||||
self.cleaned_data['value_account'] = None
|
|
||||||
super(SettingsForm, self).clean()
|
|
||||||
|
|
||||||
class FilterHistoryForm(forms.Form):
|
class FilterHistoryForm(forms.Form):
|
||||||
checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all())
|
checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all())
|
||||||
|
|
20
kfet/migrations/0053_created_at.py
Normal file
20
kfet/migrations/0053_created_at.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('kfet', '0052_category_addcost'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='account',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||||
|
),
|
||||||
|
]
|
58
kfet/migrations/0054_delete_settings.py
Normal file
58
kfet/migrations/0054_delete_settings.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
from kfet.forms import KFetConfigForm
|
||||||
|
|
||||||
|
|
||||||
|
def adapt_settings(apps, schema_editor):
|
||||||
|
Settings = apps.get_model('kfet', 'Settings')
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
obj = Settings.objects.using(db_alias)
|
||||||
|
|
||||||
|
cfg = {}
|
||||||
|
|
||||||
|
def try_get(new, old, type_field):
|
||||||
|
try:
|
||||||
|
value = getattr(obj.get(name=old), type_field)
|
||||||
|
cfg[new] = value
|
||||||
|
except Settings.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
subvention = obj.get(name='SUBVENTION_COF').value_decimal
|
||||||
|
subvention_mult = 1 + subvention/100
|
||||||
|
reduction = (1 - 1/subvention_mult) * 100
|
||||||
|
cfg['kfet_reduction_cof'] = reduction
|
||||||
|
except Settings.DoesNotExist:
|
||||||
|
pass
|
||||||
|
try_get('kfet_addcost_amount', 'ADDCOST_AMOUNT', 'value_decimal')
|
||||||
|
try_get('kfet_addcost_for', 'ADDCOST_FOR', 'value_account')
|
||||||
|
try_get('kfet_overdraft_duration', 'OVERDRAFT_DURATION', 'value_duration')
|
||||||
|
try_get('kfet_overdraft_amount', 'OVERDRAFT_AMOUNT', 'value_decimal')
|
||||||
|
try_get('kfet_cancel_duration', 'CANCEL_DURATION', 'value_duration')
|
||||||
|
|
||||||
|
cfg_form = KFetConfigForm(initial=cfg)
|
||||||
|
if cfg_form.is_valid():
|
||||||
|
cfg_form.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('kfet', '0053_created_at'),
|
||||||
|
('djconfig', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(adapt_settings),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='settings',
|
||||||
|
name='value_account',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Settings',
|
||||||
|
),
|
||||||
|
]
|
151
kfet/models.py
151
kfet/models.py
|
@ -1,12 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import (absolute_import, division,
|
|
||||||
print_function, unicode_literals)
|
|
||||||
from builtins import *
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError
|
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
|
@ -15,11 +10,12 @@ from django.utils import timezone
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.core.cache import cache
|
from datetime import date
|
||||||
from datetime import date, timedelta
|
|
||||||
import re
|
import re
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
from kfet.config import kfet_config
|
||||||
|
|
||||||
def choices_length(choices):
|
def choices_length(choices):
|
||||||
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
||||||
|
|
||||||
|
@ -41,7 +37,7 @@ class Account(models.Model):
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits = 6, decimal_places = 2,
|
||||||
default = 0)
|
default = 0)
|
||||||
is_frozen = models.BooleanField("est gelé", default = False)
|
is_frozen = models.BooleanField("est gelé", default = False)
|
||||||
created_at = models.DateTimeField(auto_now_add = True, null = True)
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
# Optional
|
# Optional
|
||||||
PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)]
|
PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)]
|
||||||
promo = models.IntegerField(
|
promo = models.IntegerField(
|
||||||
|
@ -85,7 +81,7 @@ class Account(models.Model):
|
||||||
# Propriétés supplémentaires
|
# Propriétés supplémentaires
|
||||||
@property
|
@property
|
||||||
def real_balance(self):
|
def real_balance(self):
|
||||||
if (hasattr(self, 'negative')):
|
if hasattr(self, 'negative') and self.negative.balance_offset:
|
||||||
return self.balance - self.negative.balance_offset
|
return self.balance - self.negative.balance_offset
|
||||||
return self.balance
|
return self.balance
|
||||||
|
|
||||||
|
@ -113,8 +109,8 @@ class Account(models.Model):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def perms_to_perform_operation(self, amount):
|
def perms_to_perform_operation(self, amount):
|
||||||
overdraft_duration_max = Settings.OVERDRAFT_DURATION()
|
overdraft_duration_max = kfet_config.overdraft_duration
|
||||||
overdraft_amount_max = Settings.OVERDRAFT_AMOUNT()
|
overdraft_amount_max = kfet_config.overdraft_amount
|
||||||
perms = set()
|
perms = set()
|
||||||
stop_ope = False
|
stop_ope = False
|
||||||
# Checking is cash account
|
# Checking is cash account
|
||||||
|
@ -214,6 +210,29 @@ class Account(models.Model):
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def update_negative(self):
|
||||||
|
if self.real_balance < 0:
|
||||||
|
if hasattr(self, 'negative') and not self.negative.start:
|
||||||
|
self.negative.start = timezone.now()
|
||||||
|
self.negative.save()
|
||||||
|
elif not hasattr(self, 'negative'):
|
||||||
|
self.negative = (
|
||||||
|
AccountNegative.objects.create(
|
||||||
|
account=self, start=timezone.now(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif hasattr(self, 'negative'):
|
||||||
|
# self.real_balance >= 0
|
||||||
|
balance_offset = self.negative.balance_offset
|
||||||
|
if balance_offset:
|
||||||
|
(
|
||||||
|
Account.objects
|
||||||
|
.filter(pk=self.pk)
|
||||||
|
.update(balance=F('balance')-balance_offset)
|
||||||
|
)
|
||||||
|
self.refresh_from_db()
|
||||||
|
self.negative.delete()
|
||||||
|
|
||||||
class UserHasAccount(Exception):
|
class UserHasAccount(Exception):
|
||||||
def __init__(self, trigramme):
|
def __init__(self, trigramme):
|
||||||
self.trigramme = trigramme
|
self.trigramme = trigramme
|
||||||
|
@ -632,116 +651,6 @@ class GlobalPermissions(models.Model):
|
||||||
('special_add_account', "Créer un compte avec une balance initiale")
|
('special_add_account', "Créer un compte avec une balance initiale")
|
||||||
)
|
)
|
||||||
|
|
||||||
class Settings(models.Model):
|
|
||||||
name = models.CharField(
|
|
||||||
max_length = 45,
|
|
||||||
unique = True,
|
|
||||||
db_index = True)
|
|
||||||
value_decimal = models.DecimalField(
|
|
||||||
max_digits = 6, decimal_places = 2,
|
|
||||||
blank = True, null = True, default = None)
|
|
||||||
value_account = models.ForeignKey(
|
|
||||||
Account, on_delete = models.PROTECT,
|
|
||||||
blank = True, null = True, default = None)
|
|
||||||
value_duration = models.DurationField(
|
|
||||||
blank = True, null = True, default = None)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def setting_inst(name):
|
|
||||||
return Settings.objects.get(name=name)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def SUBVENTION_COF():
|
|
||||||
subvention_cof = cache.get('SUBVENTION_COF')
|
|
||||||
if subvention_cof:
|
|
||||||
return subvention_cof
|
|
||||||
try:
|
|
||||||
subvention_cof = Settings.setting_inst("SUBVENTION_COF").value_decimal
|
|
||||||
except Settings.DoesNotExist:
|
|
||||||
subvention_cof = 0
|
|
||||||
cache.set('SUBVENTION_COF', subvention_cof)
|
|
||||||
return subvention_cof
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def ADDCOST_AMOUNT():
|
|
||||||
try:
|
|
||||||
return Settings.setting_inst("ADDCOST_AMOUNT").value_decimal
|
|
||||||
except Settings.DoesNotExist:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def ADDCOST_FOR():
|
|
||||||
try:
|
|
||||||
return Settings.setting_inst("ADDCOST_FOR").value_account
|
|
||||||
except Settings.DoesNotExist:
|
|
||||||
return None;
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def OVERDRAFT_DURATION():
|
|
||||||
overdraft_duration = cache.get('OVERDRAFT_DURATION')
|
|
||||||
if overdraft_duration:
|
|
||||||
return overdraft_duration
|
|
||||||
try:
|
|
||||||
overdraft_duration = Settings.setting_inst("OVERDRAFT_DURATION").value_duration
|
|
||||||
except Settings.DoesNotExist:
|
|
||||||
overdraft_duration = timedelta()
|
|
||||||
cache.set('OVERDRAFT_DURATION', overdraft_duration)
|
|
||||||
return overdraft_duration
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def OVERDRAFT_AMOUNT():
|
|
||||||
overdraft_amount = cache.get('OVERDRAFT_AMOUNT')
|
|
||||||
if overdraft_amount:
|
|
||||||
return overdraft_amount
|
|
||||||
try:
|
|
||||||
overdraft_amount = Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal
|
|
||||||
except Settings.DoesNotExist:
|
|
||||||
overdraft_amount = 0
|
|
||||||
cache.set('OVERDRAFT_AMOUNT', overdraft_amount)
|
|
||||||
return overdraft_amount
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def CANCEL_DURATION():
|
|
||||||
cancel_duration = cache.get('CANCEL_DURATION')
|
|
||||||
if cancel_duration:
|
|
||||||
return cancel_duration
|
|
||||||
try:
|
|
||||||
cancel_duration = Settings.setting_inst("CANCEL_DURATION").value_duration
|
|
||||||
except Settings.DoesNotExist:
|
|
||||||
cancel_duration = timedelta()
|
|
||||||
cache.set('CANCEL_DURATION', cancel_duration)
|
|
||||||
return cancel_duration
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_missing():
|
|
||||||
s, created = Settings.objects.get_or_create(name='SUBVENTION_COF')
|
|
||||||
if created:
|
|
||||||
s.value_decimal = 25
|
|
||||||
s.save()
|
|
||||||
s, created = Settings.objects.get_or_create(name='ADDCOST_AMOUNT')
|
|
||||||
if created:
|
|
||||||
s.value_decimal = 0.5
|
|
||||||
s.save()
|
|
||||||
s, created = Settings.objects.get_or_create(name='ADDCOST_FOR')
|
|
||||||
s, created = Settings.objects.get_or_create(name='OVERDRAFT_DURATION')
|
|
||||||
if created:
|
|
||||||
s.value_duration = timedelta(days=1) # 24h
|
|
||||||
s.save()
|
|
||||||
s, created = Settings.objects.get_or_create(name='OVERDRAFT_AMOUNT')
|
|
||||||
if created:
|
|
||||||
s.value_decimal = 20
|
|
||||||
s.save()
|
|
||||||
s, created = Settings.objects.get_or_create(name='CANCEL_DURATION')
|
|
||||||
if created:
|
|
||||||
s.value_duration = timedelta(minutes=5) # 5min
|
|
||||||
s.save()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def empty_cache():
|
|
||||||
cache.delete_many([
|
|
||||||
'SUBVENTION_COF', 'OVERDRAFT_DURATION', 'OVERDRAFT_AMOUNT',
|
|
||||||
'CANCEL_DURATION', 'ADDCOST_AMOUNT', 'ADDCOST_FOR',
|
|
||||||
])
|
|
||||||
|
|
||||||
class GenericTeamToken(models.Model):
|
class GenericTeamToken(models.Model):
|
||||||
token = models.CharField(max_length = 50, unique = True)
|
token = models.CharField(max_length = 50, unique = True)
|
||||||
|
|
|
@ -86,6 +86,16 @@ textarea {
|
||||||
color:#FFF;
|
color:#FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttons .nav-pills > li > a {
|
||||||
|
border-radius:0;
|
||||||
|
border:1px solid rgba(200,16,46,0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons .nav-pills > li.active > a {
|
||||||
|
background-color:rgba(200,16,46,0.9);
|
||||||
|
background-clip:padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
.row-page-header {
|
.row-page-header {
|
||||||
background-color:rgba(200,16,46,1);
|
background-color:rgba(200,16,46,1);
|
||||||
color:#FFF;
|
color:#FFF;
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="line"><b>Découvert autorisé par défaut</b></div>
|
<div class="line"><b>Découvert autorisé par défaut</b></div>
|
||||||
<div class="line">Montant: {{ settings.overdraft_amount }}€</div>
|
<div class="line">Montant: {{ kfet_config.overdraft_amount }}€</div>
|
||||||
<div class="line">Pendant: {{ settings.overdraft_duration }}</div>
|
<div class="line">Pendant: {{ kfet_config.overdraft_duration }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if perms.kfet.change_settings %}
|
{% if perms.kfet.change_settings %}
|
||||||
|
|
|
@ -23,11 +23,11 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var stat_last = new StatsGroup(
|
var stat_last = new StatsGroup(
|
||||||
"{% url 'kfet.account.stat.operation.list' trigramme=account.trigramme %}",
|
"{% url 'kfet.account.stat.operation.list' trigramme=account.trigramme %}",
|
||||||
$("#stat_last"),
|
$("#stat_last")
|
||||||
);
|
);
|
||||||
var stat_balance = new StatsGroup(
|
var stat_balance = new StatsGroup(
|
||||||
"{% url 'kfet.account.stat.balance.list' trigramme=account.trigramme %}",
|
"{% url 'kfet.account.stat.balance.list' trigramme=account.trigramme %}",
|
||||||
$("#stat_balance"),
|
$("#stat_balance")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -61,45 +61,40 @@ $(document).ready(function() {
|
||||||
<div class="col-sm-8 col-md-9 col-content-right">
|
<div class="col-sm-8 col-md-9 col-content-right">
|
||||||
{% include "kfet/base_messages.html" %}
|
{% include "kfet/base_messages.html" %}
|
||||||
<div class="content-right">
|
<div class="content-right">
|
||||||
{% if addcosts %}
|
|
||||||
<div class="content-right-block">
|
|
||||||
<h2>Gagné des majorations</h2>
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
{% for addcost in addcosts %}
|
|
||||||
<li>{{ addcost.date|date:'l j F' }}: +{{ addcost.sum_addcosts }}€</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</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"></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>
|
|
||||||
</div>
|
|
||||||
</div><!-- /row -->
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="content-right-block">
|
<div class="content-right-block">
|
||||||
<h2>Historique</h2>
|
<div class="col-sm-12 nopadding">
|
||||||
<div id="history">
|
{% if account.user == request.user %}
|
||||||
</div>
|
<div class='tab-content'>
|
||||||
</div>
|
<div class="tab-pane fade in active" id="tab_stats">
|
||||||
</div>
|
<h2>Statistiques</h2>
|
||||||
|
<div class="panel-md-margin">
|
||||||
|
<h3>Ma balance</h3>
|
||||||
|
<div id="stat_balance"></div>
|
||||||
|
<h3>Ma consommation</h3>
|
||||||
|
<div id="stat_last"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="tab_history">
|
||||||
|
{% endif %}
|
||||||
|
{% if addcosts %}
|
||||||
|
<h2>Gagné des majorations</h2>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
{% for addcost in addcosts %}
|
||||||
|
<li>{{ addcost.date|date:'l j F' }}: +{{ addcost.sum_addcosts }}€</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<h2>Historique</h2>
|
||||||
|
<div id="history"></div>
|
||||||
|
{% if account.user == request.user %}
|
||||||
|
</div>
|
||||||
|
</div><!-- tab-content -->
|
||||||
|
{% endif %}
|
||||||
|
</div><!-- col-sm-12 -->
|
||||||
|
</div><!-- content-right-block -->
|
||||||
|
</div><!-- content-right-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -20,40 +20,42 @@
|
||||||
<div class="content-right">
|
<div class="content-right">
|
||||||
<div class="content-right-block">
|
<div class="content-right-block">
|
||||||
<h2>Carte</h2>
|
<h2>Carte</h2>
|
||||||
<div class="column-row">
|
<div class="column-row">
|
||||||
<div class="column-sm-1 column-md-2 column-lg-3">
|
<div class="column-sm-1 column-md-2 column-lg-3">
|
||||||
<div class="unbreakable carte-inverted">
|
<div class="unbreakable carte-inverted">
|
||||||
<h3>Pressions du moment</h3>
|
{% if pressions %}
|
||||||
<ul class="carte">
|
<h3>Pressions du moment</h3>
|
||||||
{% for article in pressions %}
|
<ul class="carte">
|
||||||
<li class="carte-line">
|
{% for article in pressions %}
|
||||||
<div class="filler"></div>
|
<li class="carte-line">
|
||||||
<span class="carte-label">{{ article.name }}</span>
|
<div class="filler"></div>
|
||||||
<span class="carte-ukf">{{ article.price | ukf:False}} UKF</span>
|
<span class="carte-label">{{ article.name }}</span>
|
||||||
</li>
|
<span class="carte-ukf">{{ article.price | ukf:False}} UKF</span>
|
||||||
{% endfor %}
|
</li>
|
||||||
</ul>
|
|
||||||
</div><!-- endblock unbreakable -->
|
|
||||||
{% for article in articles %}
|
|
||||||
{% ifchanged article.category %}
|
|
||||||
{% if not forloop.first %}
|
|
||||||
</ul>
|
|
||||||
</div><!-- endblock unbreakable -->
|
|
||||||
{% endif %}
|
|
||||||
<div class="unbreakable">
|
|
||||||
<h3>{{ article.category.name }}</h3>
|
|
||||||
<ul class="carte">
|
|
||||||
{% endifchanged %}
|
|
||||||
<li class="carte-line">
|
|
||||||
<div class="filler"></div>
|
|
||||||
<span class="carte-label">{{ article.name }}</span>
|
|
||||||
<span class="carte-ukf">{{ article.price | ukf:False}} UKF</span>
|
|
||||||
</li>
|
|
||||||
{% if foorloop.last %}
|
|
||||||
</ul>
|
|
||||||
</div><!-- endblock unbreakable -->
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div><!-- endblock unbreakable -->
|
||||||
|
{% for article in articles %}
|
||||||
|
{% ifchanged article.category %}
|
||||||
|
{% if not forloop.first %}
|
||||||
|
</ul>
|
||||||
|
</div><!-- endblock unbreakable -->
|
||||||
|
{% endif %}
|
||||||
|
<div class="unbreakable">
|
||||||
|
<h3>{{ article.category.name }}</h3>
|
||||||
|
<ul class="carte">
|
||||||
|
{% endifchanged %}
|
||||||
|
<li class="carte-line">
|
||||||
|
<div class="filler"></div>
|
||||||
|
<span class="carte-label">{{ article.name }}</span>
|
||||||
|
<span class="carte-ukf">{{ article.price | ukf:False}} UKF</span>
|
||||||
|
</li>
|
||||||
|
{% if forloop.last %}
|
||||||
|
</ul>
|
||||||
|
</div><!-- endblock unbreakable -->
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -246,7 +246,7 @@ $(document).ready(function() {
|
||||||
var reduc_divisor = 1;
|
var reduc_divisor = 1;
|
||||||
if (kpsul.account_manager.account.is_cof)
|
if (kpsul.account_manager.account.is_cof)
|
||||||
reduc_divisor = 1 + Config.get('subvention_cof') / 100;
|
reduc_divisor = 1 + Config.get('subvention_cof') / 100;
|
||||||
return amount_euro / reduc_divisor;
|
return (amount_euro / reduc_divisor).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPurchase(article, nb) {
|
function addPurchase(article, nb) {
|
||||||
|
@ -261,7 +261,7 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
var amount_euro = amountEuroPurchase(article, nb).toFixed(2);
|
var amount_euro = amountEuroPurchase(article, nb);
|
||||||
var index = addPurchaseToFormset(article.id, nb, amount_euro);
|
var index = addPurchaseToFormset(article.id, nb, amount_euro);
|
||||||
var article_basket_html = $(item_basket_default_html);
|
var article_basket_html = $(item_basket_default_html);
|
||||||
article_basket_html
|
article_basket_html
|
||||||
|
|
|
@ -36,6 +36,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
{% if account.user == request.user %}
|
||||||
|
<ul class='nav nav-pills nav-justified'>
|
||||||
|
<li class="active"><a data-toggle="pill" href="#tab_stats">Statistiques</a></li>
|
||||||
|
<li><a data-toggle="pill" href="#tab_history">Historique</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.update' account.trigramme %}">
|
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.update' account.trigramme %}">
|
||||||
Modifier
|
Modifier
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -5,20 +5,42 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% include 'kfet/base_messages.html' %}
|
<div class="row">
|
||||||
<table>
|
<div class="col-sm-4 col-md-3 col-content-left">
|
||||||
<tr>
|
<div class="content-left">
|
||||||
<td></td>
|
<div class="buttons">
|
||||||
<td>Nom</td>
|
<a class="btn btn-primary btn-lg" href="{% url 'kfet.settings.update' %}">
|
||||||
<td>Valeur</td>
|
Modifier
|
||||||
</tr>
|
</a>
|
||||||
{% for setting in settings %}
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td><a href="{% url 'kfet.settings.update' setting.pk %}">Modifier</a></td>
|
</div>
|
||||||
<td>{{ setting.name }}</td>
|
<div class="col-sm-8 col-md-9 col-content-right">
|
||||||
<td>{% firstof setting.value_decimal setting.value_duration setting.value_account %}</td>
|
{% include 'kfet/base_messages.html' %}
|
||||||
</tr>
|
<div class="content-right">
|
||||||
{% endfor %}
|
<div class="content-right-block">
|
||||||
</table>
|
<h2>Valeurs</h2>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Nom</td>
|
||||||
|
<td>Valeur</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, value in kfet_config.list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ key }}</td>
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,14 +1,25 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
|
|
||||||
{% block title %}Modification de {{ settings.name }}{% endblock %}
|
{% block title %}Modification des paramètres{% endblock %}
|
||||||
{% block content-header-title %}Modification de {{ settings.name }}{% endblock %}
|
{% block content-header-title %}Modification des paramètres{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<form action="" method="post">
|
{% include "kfet/base_messages.html" %}
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
<div class="row form-only">
|
||||||
<input type="submit" value="Mettre à jour">
|
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||||
</form>
|
<div class="content-form">
|
||||||
|
<form submit="" method="post" class="form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include 'kfet/form_snippet.html' with form=form %}
|
||||||
|
{% if not perms.kfet.change_settings %}
|
||||||
|
{% include 'kfet/form_authentication_snippet.html' %}
|
||||||
|
{% endif %}
|
||||||
|
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from kfet.models import Settings
|
|
||||||
from math import floor
|
from math import floor
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from kfet.config import kfet_config
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,5 +41,5 @@ def highlight_clipper(clipper, q):
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
def ukf(balance, is_cof):
|
def ukf(balance, is_cof):
|
||||||
grant = is_cof and (1 + Settings.SUBVENTION_COF() / 100) or 1
|
grant = is_cof and (1 + kfet_config.subvention_cof / 100) or 1
|
||||||
return floor(balance * 10 * grant)
|
return floor(balance * 10 * grant)
|
||||||
|
|
0
kfet/tests/__init__.py
Normal file
0
kfet/tests/__init__.py
Normal file
56
kfet/tests/test_config.py
Normal file
56
kfet/tests/test_config.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
import djconfig
|
||||||
|
|
||||||
|
from gestioncof.models import User
|
||||||
|
from kfet.config import kfet_config
|
||||||
|
from kfet.models import Account
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigTest(TestCase):
|
||||||
|
"""Tests suite for kfet configuration."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# load configuration as in djconfig middleware
|
||||||
|
djconfig.reload_maybe()
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
self.assertTrue(hasattr(kfet_config, 'subvention_cof'))
|
||||||
|
|
||||||
|
def test_subvention_cof(self):
|
||||||
|
reduction_cof = Decimal('20')
|
||||||
|
subvention_cof = Decimal('25')
|
||||||
|
kfet_config.set(reduction_cof=reduction_cof)
|
||||||
|
|
||||||
|
self.assertEqual(kfet_config.subvention_cof, subvention_cof)
|
||||||
|
|
||||||
|
def test_set_decimal(self):
|
||||||
|
"""Test field of decimal type."""
|
||||||
|
reduction_cof = Decimal('10')
|
||||||
|
# IUT
|
||||||
|
kfet_config.set(reduction_cof=reduction_cof)
|
||||||
|
# check
|
||||||
|
self.assertEqual(kfet_config.reduction_cof, reduction_cof)
|
||||||
|
|
||||||
|
def test_set_modelinstance(self):
|
||||||
|
"""Test field of model instance type."""
|
||||||
|
user = User.objects.create(username='foo_user')
|
||||||
|
account = Account.objects.create(trigramme='FOO',
|
||||||
|
cofprofile=user.profile)
|
||||||
|
# IUT
|
||||||
|
kfet_config.set(addcost_for=account)
|
||||||
|
# check
|
||||||
|
self.assertEqual(kfet_config.addcost_for, account)
|
||||||
|
|
||||||
|
def test_set_duration(self):
|
||||||
|
"""Test field of duration type."""
|
||||||
|
cancel_duration = timezone.timedelta(days=2, hours=4)
|
||||||
|
# IUT
|
||||||
|
kfet_config.set(cancel_duration=cancel_duration)
|
||||||
|
# check
|
||||||
|
self.assertEqual(kfet_config.cancel_duration, cancel_duration)
|
|
@ -5,7 +5,7 @@ from unittest.mock import patch
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.contrib.auth.models import User, Permission
|
from django.contrib.auth.models import User, Permission
|
||||||
|
|
||||||
from .models import Account, Article, ArticleCategory
|
from kfet.models import Account, Article, ArticleCategory
|
||||||
|
|
||||||
|
|
||||||
class TestStats(TestCase):
|
class TestStats(TestCase):
|
|
@ -188,7 +188,7 @@ urlpatterns = [
|
||||||
permission_required('kfet.change_settings')
|
permission_required('kfet.change_settings')
|
||||||
(views.SettingsList.as_view()),
|
(views.SettingsList.as_view()),
|
||||||
name='kfet.settings'),
|
name='kfet.settings'),
|
||||||
url(r'^settings/(?P<pk>\d+)/edit$',
|
url(r'^settings/edit$',
|
||||||
permission_required('kfet.change_settings')
|
permission_required('kfet.change_settings')
|
||||||
(views.SettingsUpdate.as_view()),
|
(views.SettingsUpdate.as_view()),
|
||||||
name='kfet.settings.update'),
|
name='kfet.settings.update'),
|
||||||
|
|
115
kfet/views.py
115
kfet/views.py
|
@ -6,7 +6,7 @@ from urllib.parse import urlencode
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.views.generic import ListView, DetailView, TemplateView
|
from django.views.generic import ListView, DetailView, TemplateView, FormView
|
||||||
from django.views.generic.detail import BaseDetailView
|
from django.views.generic.detail import BaseDetailView
|
||||||
from django.views.generic.edit import CreateView, UpdateView
|
from django.views.generic.edit import CreateView, UpdateView
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
|
@ -24,9 +24,11 @@ from django.utils import timezone
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
|
|
||||||
|
from kfet.config import kfet_config
|
||||||
from kfet.decorators import teamkfet_required
|
from kfet.decorators import teamkfet_required
|
||||||
from kfet.models import (
|
from kfet.models import (
|
||||||
Account, Checkout, Article, Settings, AccountNegative,
|
Account, Checkout, Article, AccountNegative,
|
||||||
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
|
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
|
||||||
InventoryArticle, Order, OrderArticle, Operation, OperationGroup,
|
InventoryArticle, Order, OrderArticle, Operation, OperationGroup,
|
||||||
TransferGroup, Transfer, ArticleCategory)
|
TransferGroup, Transfer, ArticleCategory)
|
||||||
|
@ -37,9 +39,9 @@ from kfet.forms import (
|
||||||
GroupForm, CheckoutForm, CheckoutRestrictForm, CheckoutStatementCreateForm,
|
GroupForm, CheckoutForm, CheckoutRestrictForm, CheckoutStatementCreateForm,
|
||||||
CheckoutStatementUpdateForm, ArticleForm, ArticleRestrictForm,
|
CheckoutStatementUpdateForm, ArticleForm, ArticleRestrictForm,
|
||||||
KPsulOperationGroupForm, KPsulAccountForm, KPsulCheckoutForm,
|
KPsulOperationGroupForm, KPsulAccountForm, KPsulCheckoutForm,
|
||||||
KPsulOperationFormSet, AddcostForm, FilterHistoryForm, SettingsForm,
|
KPsulOperationFormSet, AddcostForm, FilterHistoryForm,
|
||||||
TransferFormSet, InventoryArticleForm, OrderArticleForm,
|
TransferFormSet, InventoryArticleForm, OrderArticleForm,
|
||||||
OrderArticleToInventoryForm, CategoryForm
|
OrderArticleToInventoryForm, CategoryForm, KFetConfigForm
|
||||||
)
|
)
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from kfet import consumers
|
from kfet import consumers
|
||||||
|
@ -378,8 +380,8 @@ def account_create_ajax(request, username=None, login_clipper=None,
|
||||||
@login_required
|
@login_required
|
||||||
def account_read(request, trigramme):
|
def account_read(request, trigramme):
|
||||||
try:
|
try:
|
||||||
account = Account.objects.select_related('negative')\
|
account = (Account.objects.select_related('negative')
|
||||||
.get(trigramme=trigramme)
|
.get(trigramme=trigramme))
|
||||||
except Account.DoesNotExist:
|
except Account.DoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
@ -392,7 +394,6 @@ def account_read(request, trigramme):
|
||||||
export_keys = ['id', 'trigramme', 'first_name', 'last_name', 'name',
|
export_keys = ['id', 'trigramme', 'first_name', 'last_name', 'name',
|
||||||
'email', 'is_cof', 'promo', 'balance', 'is_frozen',
|
'email', 'is_cof', 'promo', 'balance', 'is_frozen',
|
||||||
'departement', 'nickname']
|
'departement', 'nickname']
|
||||||
print(account.first_name)
|
|
||||||
data = {k: getattr(account, k) for k in export_keys}
|
data = {k: getattr(account, k) for k in export_keys}
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
@ -408,7 +409,6 @@ def account_read(request, trigramme):
|
||||||
return render(request, "kfet/account_read.html", {
|
return render(request, "kfet/account_read.html", {
|
||||||
'account': account,
|
'account': account,
|
||||||
'addcosts': addcosts,
|
'addcosts': addcosts,
|
||||||
'settings': {'subvention_cof': Settings.SUBVENTION_COF()},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -587,10 +587,6 @@ class AccountNegativeList(ListView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(AccountNegativeList, self).get_context_data(**kwargs)
|
context = super(AccountNegativeList, self).get_context_data(**kwargs)
|
||||||
context['settings'] = {
|
|
||||||
'overdraft_amount': Settings.OVERDRAFT_AMOUNT(),
|
|
||||||
'overdraft_duration': Settings.OVERDRAFT_DURATION(),
|
|
||||||
}
|
|
||||||
negs_sum = (AccountNegative.objects
|
negs_sum = (AccountNegative.objects
|
||||||
.exclude(account__trigramme='#13')
|
.exclude(account__trigramme='#13')
|
||||||
.aggregate(
|
.aggregate(
|
||||||
|
@ -961,13 +957,14 @@ def kpsul(request):
|
||||||
data['operation_formset'] = operation_formset
|
data['operation_formset'] = operation_formset
|
||||||
return render(request, 'kfet/kpsul.html', data)
|
return render(request, 'kfet/kpsul.html', data)
|
||||||
|
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
def kpsul_get_settings(request):
|
def kpsul_get_settings(request):
|
||||||
addcost_for = Settings.ADDCOST_FOR()
|
addcost_for = kfet_config.addcost_for
|
||||||
data = {
|
data = {
|
||||||
'subvention_cof': Settings.SUBVENTION_COF(),
|
'subvention_cof': kfet_config.subvention_cof,
|
||||||
'addcost_for' : addcost_for and addcost_for.trigramme or '',
|
'addcost_for': addcost_for and addcost_for.trigramme or '',
|
||||||
'addcost_amount': Settings.ADDCOST_AMOUNT(),
|
'addcost_amount': kfet_config.addcost_amount,
|
||||||
}
|
}
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
@ -991,15 +988,15 @@ def kpsul_update_addcost(request):
|
||||||
|
|
||||||
trigramme = addcost_form.cleaned_data['trigramme']
|
trigramme = addcost_form.cleaned_data['trigramme']
|
||||||
account = trigramme and Account.objects.get(trigramme=trigramme) or None
|
account = trigramme and Account.objects.get(trigramme=trigramme) or None
|
||||||
Settings.objects.filter(name='ADDCOST_FOR').update(value_account=account)
|
amount = addcost_form.cleaned_data['amount']
|
||||||
(Settings.objects.filter(name='ADDCOST_AMOUNT')
|
|
||||||
.update(value_decimal=addcost_form.cleaned_data['amount']))
|
kfet_config.set(addcost_for=account,
|
||||||
cache.delete('ADDCOST_FOR')
|
addcost_amount=amount)
|
||||||
cache.delete('ADDCOST_AMOUNT')
|
|
||||||
data = {
|
data = {
|
||||||
'addcost': {
|
'addcost': {
|
||||||
'for': trigramme and account.trigramme or None,
|
'for': account and account.trigramme or None,
|
||||||
'amount': addcost_form.cleaned_data['amount'],
|
'amount': amount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
consumers.KPsul.group_send('kfet.kpsul', data)
|
consumers.KPsul.group_send('kfet.kpsul', data)
|
||||||
|
@ -1043,10 +1040,10 @@ def kpsul_perform_operations(request):
|
||||||
operations = operation_formset.save(commit=False)
|
operations = operation_formset.save(commit=False)
|
||||||
|
|
||||||
# Retrieving COF grant
|
# Retrieving COF grant
|
||||||
cof_grant = Settings.SUBVENTION_COF()
|
cof_grant = kfet_config.subvention_cof
|
||||||
# Retrieving addcosts data
|
# Retrieving addcosts data
|
||||||
addcost_amount = Settings.ADDCOST_AMOUNT()
|
addcost_amount = kfet_config.addcost_amount
|
||||||
addcost_for = Settings.ADDCOST_FOR()
|
addcost_for = kfet_config.addcost_for
|
||||||
|
|
||||||
# Initializing vars
|
# Initializing vars
|
||||||
required_perms = set() # Required perms to perform all operations
|
required_perms = set() # Required perms to perform all operations
|
||||||
|
@ -1122,22 +1119,15 @@ def kpsul_perform_operations(request):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# If not cash account,
|
# If not cash account,
|
||||||
# saving account's balance and adding to Negative if not in
|
# saving account's balance and adding to Negative if not in
|
||||||
if not operationgroup.on_acc.is_cash:
|
on_acc = operationgroup.on_acc
|
||||||
Account.objects.filter(pk=operationgroup.on_acc.pk).update(
|
if not on_acc.is_cash:
|
||||||
balance=F('balance') + operationgroup.amount)
|
(
|
||||||
operationgroup.on_acc.refresh_from_db()
|
Account.objects
|
||||||
if operationgroup.on_acc.balance < 0:
|
.filter(pk=on_acc.pk)
|
||||||
if hasattr(operationgroup.on_acc, 'negative'):
|
.update(balance=F('balance') + operationgroup.amount)
|
||||||
if not operationgroup.on_acc.negative.start:
|
)
|
||||||
operationgroup.on_acc.negative.start = timezone.now()
|
on_acc.refresh_from_db()
|
||||||
operationgroup.on_acc.negative.save()
|
on_acc.update_negative()
|
||||||
else:
|
|
||||||
negative = AccountNegative(
|
|
||||||
account=operationgroup.on_acc, start=timezone.now())
|
|
||||||
negative.save()
|
|
||||||
elif (hasattr(operationgroup.on_acc, 'negative') and
|
|
||||||
not operationgroup.on_acc.negative.balance_offset):
|
|
||||||
operationgroup.on_acc.negative.delete()
|
|
||||||
|
|
||||||
# Updating checkout's balance
|
# Updating checkout's balance
|
||||||
if to_checkout_balance:
|
if to_checkout_balance:
|
||||||
|
@ -1272,8 +1262,9 @@ def kpsul_cancel_operations(request):
|
||||||
opes = [] # Pas déjà annulée
|
opes = [] # Pas déjà annulée
|
||||||
transfers = []
|
transfers = []
|
||||||
required_perms = set()
|
required_perms = set()
|
||||||
|
|
||||||
stop_all = False
|
stop_all = False
|
||||||
cancel_duration = Settings.CANCEL_DURATION()
|
cancel_duration = kfet_config.cancel_duration
|
||||||
# Modifs à faire sur les balances des comptes
|
# Modifs à faire sur les balances des comptes
|
||||||
to_accounts_balances = defaultdict(lambda: 0)
|
to_accounts_balances = defaultdict(lambda: 0)
|
||||||
# ------ sur les montants des groupes d'opé
|
# ------ sur les montants des groupes d'opé
|
||||||
|
@ -1282,6 +1273,7 @@ def kpsul_cancel_operations(request):
|
||||||
to_checkouts_balances = defaultdict(lambda: 0)
|
to_checkouts_balances = defaultdict(lambda: 0)
|
||||||
# ------ sur les stocks d'articles
|
# ------ sur les stocks d'articles
|
||||||
to_articles_stocks = defaultdict(lambda: 0)
|
to_articles_stocks = defaultdict(lambda: 0)
|
||||||
|
|
||||||
for ope in opes_all:
|
for ope in opes_all:
|
||||||
if ope.canceled_at:
|
if ope.canceled_at:
|
||||||
# Opération déjà annulée, va pour un warning en Response
|
# Opération déjà annulée, va pour un warning en Response
|
||||||
|
@ -1391,8 +1383,15 @@ def kpsul_cancel_operations(request):
|
||||||
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
||||||
|
|
||||||
for account in to_accounts_balances:
|
for account in to_accounts_balances:
|
||||||
Account.objects.filter(pk=account.pk).update(
|
(
|
||||||
balance=F('balance') + to_accounts_balances[account])
|
Account.objects
|
||||||
|
.filter(pk=account.pk)
|
||||||
|
.update(balance=F('balance') + to_accounts_balances[account])
|
||||||
|
)
|
||||||
|
if not account.is_cash:
|
||||||
|
# Should always be true, but we want to be sure
|
||||||
|
account.refresh_from_db()
|
||||||
|
account.update_negative()
|
||||||
for checkout in to_checkouts_balances:
|
for checkout in to_checkouts_balances:
|
||||||
Checkout.objects.filter(pk=checkout.pk).update(
|
Checkout.objects.filter(pk=checkout.pk).update(
|
||||||
balance=F('balance') + to_checkouts_balances[checkout])
|
balance=F('balance') + to_checkouts_balances[checkout])
|
||||||
|
@ -1651,34 +1650,28 @@ def kpsul_articles_data(request):
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
def history(request):
|
def history(request):
|
||||||
data = {
|
data = {
|
||||||
'filter_form': FilterHistoryForm(),
|
'filter_form': FilterHistoryForm(),
|
||||||
'settings': {
|
|
||||||
'subvention_cof': Settings.SUBVENTION_COF(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return render(request, 'kfet/history.html', data)
|
return render(request, 'kfet/history.html', data)
|
||||||
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Settings views
|
# Settings views
|
||||||
# -----
|
# -----
|
||||||
|
|
||||||
class SettingsList(ListView):
|
|
||||||
model = Settings
|
class SettingsList(TemplateView):
|
||||||
context_object_name = 'settings'
|
|
||||||
template_name = 'kfet/settings.html'
|
template_name = 'kfet/settings.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
Settings.create_missing()
|
|
||||||
return super(SettingsList, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
class SettingsUpdate(SuccessMessageMixin, UpdateView):
|
class SettingsUpdate(SuccessMessageMixin, FormView):
|
||||||
model = Settings
|
form_class = KFetConfigForm
|
||||||
form_class = SettingsForm
|
|
||||||
template_name = 'kfet/settings_update.html'
|
template_name = 'kfet/settings_update.html'
|
||||||
success_message = 'Paramètre %(name)s mis à jour'
|
success_message = 'Paramètres mis à jour'
|
||||||
success_url = reverse_lazy('kfet.settings')
|
success_url = reverse_lazy('kfet.settings')
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -1686,9 +1679,9 @@ class SettingsUpdate(SuccessMessageMixin, UpdateView):
|
||||||
if not self.request.user.has_perm('kfet.change_settings'):
|
if not self.request.user.has_perm('kfet.change_settings'):
|
||||||
form.add_error(None, 'Permission refusée')
|
form.add_error(None, 'Permission refusée')
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
# Creating
|
form.save()
|
||||||
Settings.empty_cache()
|
return super().form_valid(form)
|
||||||
return super(SettingsUpdate, self).form_valid(form)
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Transfer views
|
# Transfer views
|
||||||
|
|
|
@ -3,6 +3,7 @@ Django==1.8.*
|
||||||
django-autocomplete-light==2.3.3
|
django-autocomplete-light==2.3.3
|
||||||
django-autoslug==1.9.3
|
django-autoslug==1.9.3
|
||||||
django-cas-ng==3.5.5
|
django-cas-ng==3.5.5
|
||||||
|
django-djconfig==0.5.3
|
||||||
django-grappelli==2.8.1
|
django-grappelli==2.8.1
|
||||||
django-recaptcha==1.0.5
|
django-recaptcha==1.0.5
|
||||||
mysqlclient==1.3.7
|
mysqlclient==1.3.7
|
||||||
|
@ -11,14 +12,14 @@ six==1.10.0
|
||||||
unicodecsv==0.14.1
|
unicodecsv==0.14.1
|
||||||
icalendar==3.10
|
icalendar==3.10
|
||||||
django-bootstrap-form==3.2.1
|
django-bootstrap-form==3.2.1
|
||||||
asgiref==0.14.0
|
asgiref==1.1.1
|
||||||
daphne==0.14.3
|
daphne==1.2.0
|
||||||
asgi-redis==0.14.0
|
asgi-redis==1.3.0
|
||||||
statistics==1.0.3.5
|
statistics==1.0.3.5
|
||||||
future==0.15.2
|
future==0.15.2
|
||||||
django-widget-tweaks==1.4.1
|
django-widget-tweaks==1.4.1
|
||||||
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
||||||
ldap3
|
ldap3
|
||||||
git+https://github.com/Aureplop/channels.git#egg=channels
|
channels==1.1.3
|
||||||
django-js-reverse==0.7.3
|
django-js-reverse==0.7.3
|
||||||
python-dateutil
|
python-dateutil
|
||||||
|
|
Loading…
Reference in a new issue